grape 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/CONTRIBUTING.md +2 -2
- data/README.md +2 -2
- data/UPGRADING.md +14 -0
- data/grape.gemspec +2 -2
- data/lib/grape/api/instance.rb +16 -47
- data/lib/grape/api.rb +6 -10
- data/lib/grape/content_types.rb +1 -4
- data/lib/grape/declared_params_handler.rb +118 -0
- data/lib/grape/dsl/declared.rb +35 -0
- data/lib/grape/dsl/helpers.rb +2 -2
- data/lib/grape/dsl/inside_route.rb +3 -141
- data/lib/grape/dsl/parameters.rb +8 -26
- data/lib/grape/dsl/routing.rb +41 -29
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/dsl/validations.rb +22 -20
- data/lib/grape/endpoint.rb +84 -83
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/middleware/auth/dsl.rb +4 -4
- data/lib/grape/middleware/base.rb +4 -0
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +6 -4
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/middleware/versioner/base.rb +20 -0
- data/lib/grape/middleware/versioner/header.rb +1 -17
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +5 -9
- data/lib/grape/params_builder.rb +2 -19
- data/lib/grape/router/base_route.rb +14 -5
- data/lib/grape/router/greedy_route.rb +11 -5
- data/lib/grape/router/pattern.rb +6 -20
- data/lib/grape/router/route.rb +7 -11
- data/lib/grape/router.rb +35 -61
- data/lib/grape/util/api_description.rb +10 -8
- data/lib/grape/validations/attributes_iterator.rb +2 -2
- data/lib/grape/validations/params_scope.rb +12 -10
- data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
- data/lib/grape/validations/validators/base.rb +2 -2
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/{mutual_exclusion_validator.rb → mutually_exclusive_validator.rb} +1 -1
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +12 -2
- data/lib/grape/validations/validators/values_validator.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +5 -13
- metadata +11 -14
- data/lib/grape/exceptions/missing_option.rb +0 -11
- data/lib/grape/exceptions/unknown_options.rb +0 -11
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +0 -24
- data/lib/grape/extensions/hash.rb +0 -27
- data/lib/grape/extensions/hashie/mash.rb +0 -24
data/lib/grape/dsl/parameters.rb
CHANGED
|
@@ -160,33 +160,15 @@ module Grape
|
|
|
160
160
|
# Define common settings for one or more parameters
|
|
161
161
|
# @param (see #requires)
|
|
162
162
|
# @option (see #requires)
|
|
163
|
-
def with(*attrs, &
|
|
163
|
+
def with(*attrs, &)
|
|
164
164
|
new_group_attrs = [@group, attrs.clone.first].compact.reduce(&:deep_merge)
|
|
165
|
-
new_group_scope([new_group_attrs], &
|
|
165
|
+
new_group_scope([new_group_attrs], &)
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Require exactly one of the given parameters to be present.
|
|
175
|
-
# @param (see #mutually_exclusive)
|
|
176
|
-
def exactly_one_of(*attrs)
|
|
177
|
-
validates(attrs, exactly_one_of: { value: true, message: extract_message_option(attrs) })
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Require at least one of the given parameters to be present.
|
|
181
|
-
# @param (see #mutually_exclusive)
|
|
182
|
-
def at_least_one_of(*attrs)
|
|
183
|
-
validates(attrs, at_least_one_of: { value: true, message: extract_message_option(attrs) })
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Require that either all given params are present, or none are.
|
|
187
|
-
# @param (see #mutually_exclusive)
|
|
188
|
-
def all_or_none_of(*attrs)
|
|
189
|
-
validates(attrs, all_or_none_of: { value: true, message: extract_message_option(attrs) })
|
|
168
|
+
%i[mutually_exclusive exactly_one_of at_least_one_of all_or_none_of].each do |validator|
|
|
169
|
+
define_method validator do |*attrs, message: nil|
|
|
170
|
+
validates(attrs, validator => { value: true, message: message })
|
|
171
|
+
end
|
|
190
172
|
end
|
|
191
173
|
|
|
192
174
|
# Define a block of validations which should be applied if and only if
|
|
@@ -196,12 +178,12 @@ module Grape
|
|
|
196
178
|
# @raise Grape::Exceptions::UnknownParameter if `attr` has not been
|
|
197
179
|
# defined in this scope yet
|
|
198
180
|
# @yield a parameter definition DSL
|
|
199
|
-
def given(*attrs, &
|
|
181
|
+
def given(*attrs, &)
|
|
200
182
|
attrs.each do |attr|
|
|
201
183
|
proxy_attr = first_hash_key_or_param(attr)
|
|
202
184
|
raise Grape::Exceptions::UnknownParameter.new(proxy_attr) unless declared_param?(proxy_attr)
|
|
203
185
|
end
|
|
204
|
-
new_lateral_scope(dependent_on: attrs, &
|
|
186
|
+
new_lateral_scope(dependent_on: attrs, &)
|
|
205
187
|
end
|
|
206
188
|
|
|
207
189
|
# Test for whether a certain parameter has been defined in this params
|
data/lib/grape/dsl/routing.rb
CHANGED
|
@@ -5,6 +5,22 @@ module Grape
|
|
|
5
5
|
module Routing
|
|
6
6
|
attr_reader :endpoints
|
|
7
7
|
|
|
8
|
+
def given(conditional_option, &)
|
|
9
|
+
return unless conditional_option
|
|
10
|
+
|
|
11
|
+
mounted(&)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def mounted(&block)
|
|
15
|
+
evaluate_as_instance_with_configuration(block, lazy: true)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cascade(value = nil)
|
|
19
|
+
return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable[:cascade].nil? : true if value.nil?
|
|
20
|
+
|
|
21
|
+
inheritable_setting.namespace_inheritable[:cascade] = value
|
|
22
|
+
end
|
|
23
|
+
|
|
8
24
|
# Specify an API version.
|
|
9
25
|
#
|
|
10
26
|
# @example API with legacy support.
|
|
@@ -141,7 +157,7 @@ module Grape
|
|
|
141
157
|
# {hello: 'world'}
|
|
142
158
|
# end
|
|
143
159
|
# end
|
|
144
|
-
def route(methods, paths = ['/'], route_options = {}, &
|
|
160
|
+
def route(methods, paths = ['/'], route_options = {}, &)
|
|
145
161
|
method = methods == :any ? '*' : methods
|
|
146
162
|
endpoint_params = inheritable_setting.namespace_stackable_with_hash(:params) || {}
|
|
147
163
|
endpoint_description = inheritable_setting.route[:description]
|
|
@@ -149,14 +165,14 @@ module Grape
|
|
|
149
165
|
all_route_options.deep_merge!(endpoint_description) if endpoint_description
|
|
150
166
|
all_route_options.deep_merge!(route_options) if route_options&.any?
|
|
151
167
|
|
|
152
|
-
|
|
168
|
+
new_endpoint = Grape::Endpoint.new(
|
|
169
|
+
inheritable_setting,
|
|
153
170
|
method: method,
|
|
154
171
|
path: paths,
|
|
155
172
|
for: self,
|
|
156
|
-
route_options: all_route_options
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
new_endpoint = Grape::Endpoint.new(inheritable_setting, endpoint_options, &block)
|
|
173
|
+
route_options: all_route_options,
|
|
174
|
+
&
|
|
175
|
+
)
|
|
160
176
|
endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }
|
|
161
177
|
|
|
162
178
|
inheritable_setting.route_end
|
|
@@ -182,12 +198,12 @@ module Grape
|
|
|
182
198
|
# # defines the endpoint: GET /foo/bar
|
|
183
199
|
# end
|
|
184
200
|
# end
|
|
185
|
-
def namespace(space = nil,
|
|
201
|
+
def namespace(space = nil, requirements: nil, **options, &block)
|
|
186
202
|
return Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace]) unless space || block
|
|
187
203
|
|
|
188
204
|
within_namespace do
|
|
189
205
|
nest(block) do
|
|
190
|
-
inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, options) if space
|
|
206
|
+
inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, requirements: requirements, **options) if space
|
|
191
207
|
end
|
|
192
208
|
end
|
|
193
209
|
end
|
|
@@ -202,33 +218,19 @@ module Grape
|
|
|
202
218
|
@routes ||= endpoints.map(&:routes).flatten
|
|
203
219
|
end
|
|
204
220
|
|
|
205
|
-
# Remove all defined routes.
|
|
206
|
-
def reset_routes!
|
|
207
|
-
endpoints.each(&:reset_routes!)
|
|
208
|
-
@routes = nil
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def reset_endpoints!
|
|
212
|
-
@endpoints = []
|
|
213
|
-
end
|
|
214
|
-
|
|
215
221
|
# This method allows you to quickly define a parameter route segment
|
|
216
222
|
# in your API.
|
|
217
223
|
#
|
|
218
224
|
# @param param [Symbol] The name of the parameter you wish to declare.
|
|
219
225
|
# @option options [Regexp] You may supply a regular expression that the declared parameter must meet.
|
|
220
|
-
def route_param(param,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
options[:requirements] = {
|
|
224
|
-
param.to_sym => options[:requirements]
|
|
225
|
-
} if options[:requirements].is_a?(Regexp)
|
|
226
|
+
def route_param(param, requirements: nil, type: nil, **options, &)
|
|
227
|
+
requirements = { param.to_sym => requirements } if requirements.is_a?(Regexp)
|
|
226
228
|
|
|
227
229
|
Grape::Validations::ParamsScope.new(api: self) do
|
|
228
|
-
requires param, type:
|
|
229
|
-
end if
|
|
230
|
+
requires param, type: type
|
|
231
|
+
end if type
|
|
230
232
|
|
|
231
|
-
namespace(":#{param}", options, &
|
|
233
|
+
namespace(":#{param}", requirements: requirements, **options, &)
|
|
232
234
|
end
|
|
233
235
|
|
|
234
236
|
# @return array of defined versions
|
|
@@ -238,6 +240,16 @@ module Grape
|
|
|
238
240
|
|
|
239
241
|
private
|
|
240
242
|
|
|
243
|
+
# Remove all defined routes.
|
|
244
|
+
def reset_routes!
|
|
245
|
+
endpoints.each(&:reset_routes!)
|
|
246
|
+
@routes = nil
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def reset_endpoints!
|
|
250
|
+
@endpoints = []
|
|
251
|
+
end
|
|
252
|
+
|
|
241
253
|
def refresh_mounted_api(mounts, *opts)
|
|
242
254
|
opts << { refresh_already_mounted: true }
|
|
243
255
|
mount(mounts, *opts)
|
|
@@ -260,12 +272,12 @@ module Grape
|
|
|
260
272
|
def evaluate_as_instance_with_configuration(block, lazy: false)
|
|
261
273
|
lazy_block = Grape::Util::Lazy::Block.new do |configuration|
|
|
262
274
|
value_for_configuration = configuration
|
|
263
|
-
self.configuration = value_for_configuration.evaluate if value_for_configuration.
|
|
275
|
+
self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?
|
|
264
276
|
response = instance_eval(&block)
|
|
265
277
|
self.configuration = value_for_configuration
|
|
266
278
|
response
|
|
267
279
|
end
|
|
268
|
-
if base && base_instance? && lazy
|
|
280
|
+
if @base && base_instance? && lazy
|
|
269
281
|
lazy_block
|
|
270
282
|
else
|
|
271
283
|
lazy_block.evaluate_from(configuration)
|
data/lib/grape/dsl/settings.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
# matter where they're defined, and inheritable settings which apply only
|
|
8
8
|
# in the current scope and scopes nested under it.
|
|
9
9
|
module Settings
|
|
10
|
-
attr_writer :inheritable_setting
|
|
10
|
+
attr_writer :inheritable_setting
|
|
11
11
|
|
|
12
12
|
# Fetch our top-level settings, which apply to all endpoints in the API.
|
|
13
13
|
def top_level_setting
|
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
module Grape
|
|
4
4
|
module DSL
|
|
5
5
|
module Validations
|
|
6
|
+
# Opens a root-level ParamsScope, defining parameter coercions and
|
|
7
|
+
# validations for the endpoint.
|
|
8
|
+
# @yield instance context of the new scope
|
|
9
|
+
def params(&)
|
|
10
|
+
Grape::Validations::ParamsScope.new(api: self, type: Hash, &)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Declare the contract to be used for the endpoint's parameters.
|
|
14
|
+
# @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
|
|
15
|
+
# The contract or schema to be used for validation. Optional.
|
|
16
|
+
# @yield a block yielding a new instance of Dry::Schema::Params
|
|
17
|
+
# subclass, allowing to define the schema inline. When the
|
|
18
|
+
# +contract+ parameter is a schema, it will be used as a parent. Optional.
|
|
19
|
+
def contract(contract = nil, &block)
|
|
20
|
+
raise ArgumentError, 'Either contract or block must be provided' unless contract || block
|
|
21
|
+
raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
|
|
22
|
+
|
|
23
|
+
Grape::Validations::ContractScope.new(self, contract, &block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
6
28
|
# Clears all defined parameters and validations. The main purpose of it is to clean up
|
|
7
29
|
# settings, so next endpoint won't interfere with previous one.
|
|
8
30
|
#
|
|
@@ -24,26 +46,6 @@ module Grape
|
|
|
24
46
|
def reset_validations!
|
|
25
47
|
inheritable_setting.namespace_stackable.delete(:declared_params, :params, :validations)
|
|
26
48
|
end
|
|
27
|
-
|
|
28
|
-
# Opens a root-level ParamsScope, defining parameter coercions and
|
|
29
|
-
# validations for the endpoint.
|
|
30
|
-
# @yield instance context of the new scope
|
|
31
|
-
def params(&block)
|
|
32
|
-
Grape::Validations::ParamsScope.new(api: self, type: Hash, &block)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Declare the contract to be used for the endpoint's parameters.
|
|
36
|
-
# @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]
|
|
37
|
-
# The contract or schema to be used for validation. Optional.
|
|
38
|
-
# @yield a block yielding a new instance of Dry::Schema::Params
|
|
39
|
-
# subclass, allowing to define the schema inline. When the
|
|
40
|
-
# +contract+ parameter is a schema, it will be used as a parent. Optional.
|
|
41
|
-
def contract(contract = nil, &block)
|
|
42
|
-
raise ArgumentError, 'Either contract or block must be provided' unless contract || block
|
|
43
|
-
raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)
|
|
44
|
-
|
|
45
|
-
Grape::Validations::ContractScope.new(self, contract, &block)
|
|
46
|
-
end
|
|
47
49
|
end
|
|
48
50
|
end
|
|
49
51
|
end
|
data/lib/grape/endpoint.rb
CHANGED
|
@@ -30,7 +30,16 @@ module Grape
|
|
|
30
30
|
|
|
31
31
|
def run_before_each(endpoint)
|
|
32
32
|
superclass.run_before_each(endpoint) unless self == Endpoint
|
|
33
|
-
before_each.each { |blk| blk.
|
|
33
|
+
before_each.each { |blk| blk.call(endpoint) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def block_to_unbound_method(block)
|
|
37
|
+
return unless block
|
|
38
|
+
|
|
39
|
+
define_method :temp_unbound_method, block
|
|
40
|
+
method = instance_method(:temp_unbound_method)
|
|
41
|
+
remove_method :temp_unbound_method
|
|
42
|
+
method
|
|
34
43
|
end
|
|
35
44
|
end
|
|
36
45
|
|
|
@@ -46,10 +55,7 @@ module Grape
|
|
|
46
55
|
# @note This happens at the time of API definition, so in this context the
|
|
47
56
|
# endpoint does not know if it will be mounted under a different endpoint.
|
|
48
57
|
# @yield a block defining what your API should do when this endpoint is hit
|
|
49
|
-
def initialize(new_settings, options
|
|
50
|
-
require_option(options, :path)
|
|
51
|
-
require_option(options, :method)
|
|
52
|
-
|
|
58
|
+
def initialize(new_settings, **options, &block)
|
|
53
59
|
self.inheritable_setting = new_settings.point_in_time_copy
|
|
54
60
|
|
|
55
61
|
# now +namespace_stackable(:declared_params)+ contains all params defined for
|
|
@@ -65,16 +71,13 @@ module Grape
|
|
|
65
71
|
|
|
66
72
|
@options[:path] = Array(options[:path])
|
|
67
73
|
@options[:path] << '/' if options[:path].empty?
|
|
68
|
-
|
|
69
74
|
@options[:method] = Array(options[:method])
|
|
70
|
-
@options[:route_options] ||= {}
|
|
71
75
|
|
|
72
|
-
@lazy_initialize_lock = Mutex.new
|
|
73
|
-
@lazy_initialized = nil
|
|
74
76
|
@status = nil
|
|
75
77
|
@stream = nil
|
|
76
78
|
@body = nil
|
|
77
|
-
@source = block
|
|
79
|
+
@source = self.class.block_to_unbound_method(block)
|
|
80
|
+
@before_filter_passed = false
|
|
78
81
|
end
|
|
79
82
|
|
|
80
83
|
# Update our settings from a given set of stackable parameters. Used when
|
|
@@ -88,10 +91,6 @@ module Grape
|
|
|
88
91
|
endpoints&.each { |e| e.inherit_settings(namespace_stackable) }
|
|
89
92
|
end
|
|
90
93
|
|
|
91
|
-
def require_option(options, key)
|
|
92
|
-
raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
94
|
def routes
|
|
96
95
|
@routes ||= endpoints&.collect(&:routes)&.flatten || to_routes
|
|
97
96
|
end
|
|
@@ -103,9 +102,13 @@ module Grape
|
|
|
103
102
|
end
|
|
104
103
|
|
|
105
104
|
def mount_in(router)
|
|
106
|
-
|
|
105
|
+
if endpoints
|
|
106
|
+
compile!
|
|
107
|
+
return endpoints.each { |e| e.mount_in(router) }
|
|
108
|
+
end
|
|
107
109
|
|
|
108
110
|
reset_routes!
|
|
111
|
+
compile!
|
|
109
112
|
routes.each do |route|
|
|
110
113
|
router.append(route.apply(self))
|
|
111
114
|
next unless !inheritable_setting.namespace_inheritable[:do_not_route_head] && route.request_method == Rack::GET
|
|
@@ -117,59 +120,11 @@ module Grape
|
|
|
117
120
|
end
|
|
118
121
|
end
|
|
119
122
|
|
|
120
|
-
def to_routes
|
|
121
|
-
default_route_options = prepare_default_route_attributes
|
|
122
|
-
|
|
123
|
-
map_routes do |method, raw_path|
|
|
124
|
-
prepared_path = Path.new(raw_path, namespace, prepare_default_path_settings)
|
|
125
|
-
params = options[:route_options].present? ? options[:route_options].merge(default_route_options) : default_route_options
|
|
126
|
-
route = Grape::Router::Route.new(method, prepared_path.origin, prepared_path.suffix, params)
|
|
127
|
-
route.apply(self)
|
|
128
|
-
end.flatten
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def prepare_routes_requirements
|
|
132
|
-
{}.merge!(*inheritable_setting.namespace_stackable[:namespace].map(&:requirements)).tap do |requirements|
|
|
133
|
-
endpoint_requirements = options.dig(:route_options, :requirements)
|
|
134
|
-
requirements.merge!(endpoint_requirements) if endpoint_requirements
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def prepare_default_route_attributes
|
|
139
|
-
{
|
|
140
|
-
namespace: namespace,
|
|
141
|
-
version: prepare_version,
|
|
142
|
-
requirements: prepare_routes_requirements,
|
|
143
|
-
prefix: inheritable_setting.namespace_inheritable[:root_prefix],
|
|
144
|
-
anchor: options[:route_options].fetch(:anchor, true),
|
|
145
|
-
settings: inheritable_setting.route.except(:declared_params, :saved_validations),
|
|
146
|
-
forward_match: options[:forward_match]
|
|
147
|
-
}
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def prepare_version
|
|
151
|
-
version = inheritable_setting.namespace_inheritable[:version]
|
|
152
|
-
return if version.blank?
|
|
153
|
-
|
|
154
|
-
version.length == 1 ? version.first : version
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def map_routes
|
|
158
|
-
options[:method].map { |method| options[:path].map { |path| yield method, path } }
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def prepare_default_path_settings
|
|
162
|
-
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
|
|
163
|
-
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
|
|
164
|
-
namespace_stackable_hash.merge!(namespace_inheritable_hash)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
123
|
def namespace
|
|
168
124
|
@namespace ||= Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace])
|
|
169
125
|
end
|
|
170
126
|
|
|
171
127
|
def call(env)
|
|
172
|
-
lazy_initialize!
|
|
173
128
|
dup.call!(env)
|
|
174
129
|
end
|
|
175
130
|
|
|
@@ -184,7 +139,7 @@ module Grape
|
|
|
184
139
|
# Return the collection of endpoints within this endpoint.
|
|
185
140
|
# This is the case when an Grape::API mounts another Grape::API.
|
|
186
141
|
def endpoints
|
|
187
|
-
@endpoints ||= options[:app].
|
|
142
|
+
@endpoints ||= options[:app].respond_to?(:endpoints) ? options[:app].endpoints : nil
|
|
188
143
|
end
|
|
189
144
|
|
|
190
145
|
def equals?(endpoint)
|
|
@@ -208,6 +163,7 @@ module Grape
|
|
|
208
163
|
begin
|
|
209
164
|
self.class.run_before_each(self)
|
|
210
165
|
run_filters befores, :before
|
|
166
|
+
@before_filter_passed = true
|
|
211
167
|
|
|
212
168
|
if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)
|
|
213
169
|
header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')
|
|
@@ -243,22 +199,7 @@ module Grape
|
|
|
243
199
|
return unless @source
|
|
244
200
|
|
|
245
201
|
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do
|
|
246
|
-
|
|
247
|
-
rescue LocalJumpError => e
|
|
248
|
-
Grape.deprecator.warn 'Using `return` in an endpoint has been deprecated. Use `next` instead.'
|
|
249
|
-
return e.exit_value
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def lazy_initialize!
|
|
254
|
-
return true if @lazy_initialized
|
|
255
|
-
|
|
256
|
-
@lazy_initialize_lock.synchronize do
|
|
257
|
-
return true if @lazy_initialized
|
|
258
|
-
|
|
259
|
-
@app = options[:app] || build_stack
|
|
260
|
-
@helpers = build_helpers
|
|
261
|
-
@lazy_initialized = true
|
|
202
|
+
@source.bind_call(self)
|
|
262
203
|
end
|
|
263
204
|
end
|
|
264
205
|
|
|
@@ -281,11 +222,11 @@ module Grape
|
|
|
281
222
|
end
|
|
282
223
|
|
|
283
224
|
def run_filters(filters, type = :other)
|
|
225
|
+
return unless filters
|
|
226
|
+
|
|
284
227
|
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
|
|
285
|
-
filters
|
|
228
|
+
filters.each { |filter| instance_eval(&filter) }
|
|
286
229
|
end
|
|
287
|
-
post_extension = DSL::InsideRoute.post_filter_methods(type)
|
|
288
|
-
extend post_extension if post_extension
|
|
289
230
|
end
|
|
290
231
|
|
|
291
232
|
%i[befores before_validations after_validations afters finallies].each do |method|
|
|
@@ -311,6 +252,66 @@ module Grape
|
|
|
311
252
|
|
|
312
253
|
private
|
|
313
254
|
|
|
255
|
+
attr_reader :before_filter_passed
|
|
256
|
+
|
|
257
|
+
def compile!
|
|
258
|
+
@app = options[:app] || build_stack
|
|
259
|
+
@helpers = build_helpers
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def to_routes
|
|
263
|
+
route_options = options[:route_options]
|
|
264
|
+
default_route_options = prepare_default_route_attributes(route_options)
|
|
265
|
+
complete_route_options = route_options.merge(default_route_options)
|
|
266
|
+
path_settings = prepare_default_path_settings
|
|
267
|
+
|
|
268
|
+
options[:method].flat_map do |method|
|
|
269
|
+
options[:path].map do |path|
|
|
270
|
+
prepared_path = Path.new(path, default_route_options[:namespace], path_settings)
|
|
271
|
+
pattern = Grape::Router::Pattern.new(
|
|
272
|
+
origin: prepared_path.origin,
|
|
273
|
+
suffix: prepared_path.suffix,
|
|
274
|
+
anchor: default_route_options[:anchor],
|
|
275
|
+
params: route_options[:params],
|
|
276
|
+
format: options[:format],
|
|
277
|
+
version: default_route_options[:version],
|
|
278
|
+
requirements: default_route_options[:requirements]
|
|
279
|
+
)
|
|
280
|
+
Grape::Router::Route.new(self, method, pattern, complete_route_options)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def prepare_default_route_attributes(route_options)
|
|
286
|
+
{
|
|
287
|
+
namespace: namespace,
|
|
288
|
+
version: prepare_version(inheritable_setting.namespace_inheritable[:version]),
|
|
289
|
+
requirements: prepare_routes_requirements(route_options[:requirements]),
|
|
290
|
+
prefix: inheritable_setting.namespace_inheritable[:root_prefix],
|
|
291
|
+
anchor: route_options.fetch(:anchor, true),
|
|
292
|
+
settings: inheritable_setting.route.except(:declared_params, :saved_validations),
|
|
293
|
+
forward_match: options[:forward_match]
|
|
294
|
+
}
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def prepare_default_path_settings
|
|
298
|
+
namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash
|
|
299
|
+
namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash
|
|
300
|
+
namespace_stackable_hash.merge!(namespace_inheritable_hash)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def prepare_routes_requirements(route_options_requirements)
|
|
304
|
+
namespace_requirements = inheritable_setting.namespace_stackable[:namespace].filter_map(&:requirements)
|
|
305
|
+
namespace_requirements << route_options_requirements if route_options_requirements.present?
|
|
306
|
+
namespace_requirements.reduce({}, :merge)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def prepare_version(namespace_inheritable_version)
|
|
310
|
+
return if namespace_inheritable_version.blank?
|
|
311
|
+
|
|
312
|
+
namespace_inheritable_version.length == 1 ? namespace_inheritable_version.first : namespace_inheritable_version
|
|
313
|
+
end
|
|
314
|
+
|
|
314
315
|
def build_stack
|
|
315
316
|
stack = Grape::Middleware::Stack.new
|
|
316
317
|
|
|
@@ -65,7 +65,7 @@ module Grape
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def fallback_message(key, options)
|
|
68
|
-
if ::I18n.enforce_available_locales &&
|
|
68
|
+
if ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)
|
|
69
69
|
key
|
|
70
70
|
else
|
|
71
71
|
::I18n.translate(key, locale: FALLBACK_LOCALE, **options)
|
|
@@ -16,12 +16,12 @@ module Grape
|
|
|
16
16
|
#
|
|
17
17
|
# @param [Hash] options A hash of options.
|
|
18
18
|
# @option options [String] :realm "API Authorization" The HTTP Basic realm.
|
|
19
|
-
def http_basic(options = {}, &
|
|
19
|
+
def http_basic(options = {}, &)
|
|
20
20
|
options[:realm] ||= 'API Authorization'
|
|
21
|
-
auth
|
|
21
|
+
auth(:http_basic, options, &)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def http_digest(options = {}, &
|
|
24
|
+
def http_digest(options = {}, &)
|
|
25
25
|
options[:realm] ||= 'API Authorization'
|
|
26
26
|
|
|
27
27
|
if options[:realm].respond_to?(:values_at)
|
|
@@ -30,7 +30,7 @@ module Grape
|
|
|
30
30
|
options[:opaque] ||= 'secret'
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
auth
|
|
33
|
+
auth(:http_digest, options, &)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -58,7 +58,7 @@ module Grape
|
|
|
58
58
|
h.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
|
59
59
|
end
|
|
60
60
|
backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []
|
|
61
|
-
original_exception = error.is_a?(Exception) ? error : error[:original_exception]
|
|
61
|
+
original_exception = error.is_a?(Exception) ? error : error[:original_exception]
|
|
62
62
|
rack_response(status, headers, format_message(message, backtrace, original_exception))
|
|
63
63
|
end
|
|
64
64
|
|
|
@@ -69,12 +69,14 @@ module Grape
|
|
|
69
69
|
return if input.nil?
|
|
70
70
|
return unless read_body_input?
|
|
71
71
|
|
|
72
|
-
input.
|
|
72
|
+
rewind = input.respond_to?(:rewind)
|
|
73
|
+
|
|
74
|
+
input.rewind if rewind
|
|
73
75
|
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
|
|
74
76
|
begin
|
|
75
77
|
read_rack_input(body)
|
|
76
78
|
ensure
|
|
77
|
-
input.
|
|
79
|
+
input.rewind if rewind
|
|
78
80
|
end
|
|
79
81
|
end
|
|
80
82
|
|
|
@@ -130,7 +132,7 @@ module Grape
|
|
|
130
132
|
end
|
|
131
133
|
|
|
132
134
|
def format_from_extension
|
|
133
|
-
request_path = rack_request.path
|
|
135
|
+
request_path = try_scrub(rack_request.path)
|
|
134
136
|
dot_pos = request_path.rindex('.')
|
|
135
137
|
return unless dot_pos
|
|
136
138
|
|
|
@@ -139,7 +141,7 @@ module Grape
|
|
|
139
141
|
end
|
|
140
142
|
|
|
141
143
|
def format_from_header
|
|
142
|
-
accept_header = env['HTTP_ACCEPT']
|
|
144
|
+
accept_header = try_scrub(env['HTTP_ACCEPT'])
|
|
143
145
|
return if accept_header.blank? || accept_header == ALL_MEDIA_TYPES
|
|
144
146
|
|
|
145
147
|
media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)
|
|
@@ -18,7 +18,7 @@ module Grape
|
|
|
18
18
|
# route.
|
|
19
19
|
class AcceptVersionHeader < Base
|
|
20
20
|
def before
|
|
21
|
-
potential_version = env['HTTP_ACCEPT_VERSION']
|
|
21
|
+
potential_version = try_scrub(env['HTTP_ACCEPT_VERSION'])
|
|
22
22
|
not_acceptable!('Accept-Version header must be set.') if strict && potential_version.blank?
|
|
23
23
|
|
|
24
24
|
return if potential_version.blank?
|
|
@@ -50,6 +50,26 @@ module Grape
|
|
|
50
50
|
def version_not_found!
|
|
51
51
|
throw :error, status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER
|
|
52
52
|
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def available_media_types
|
|
57
|
+
@available_media_types ||= begin
|
|
58
|
+
media_types = []
|
|
59
|
+
base_media_type = "application/vnd.#{vendor}"
|
|
60
|
+
content_types.each_key do |extension|
|
|
61
|
+
versions&.reverse_each do |version|
|
|
62
|
+
media_types << "#{base_media_type}-#{version}+#{extension}"
|
|
63
|
+
media_types << "#{base_media_type}-#{version}"
|
|
64
|
+
end
|
|
65
|
+
media_types << "#{base_media_type}+#{extension}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
media_types << base_media_type
|
|
69
|
+
media_types.concat(content_types.values.flatten)
|
|
70
|
+
media_types
|
|
71
|
+
end
|
|
72
|
+
end
|
|
53
73
|
end
|
|
54
74
|
end
|
|
55
75
|
end
|
|
@@ -101,26 +101,10 @@ module Grape
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
def version_not_found!(media_types)
|
|
104
|
-
return unless media_types.all? { |media_type| media_type&.version && versions
|
|
104
|
+
return unless media_types.all? { |media_type| media_type&.version && versions && !versions.include?(media_type.version) }
|
|
105
105
|
|
|
106
106
|
invalid_version_header!('API version not found.')
|
|
107
107
|
end
|
|
108
|
-
|
|
109
|
-
def available_media_types
|
|
110
|
-
[].tap do |available_media_types|
|
|
111
|
-
base_media_type = "application/vnd.#{vendor}"
|
|
112
|
-
content_types.each_key do |extension|
|
|
113
|
-
versions&.reverse_each do |version|
|
|
114
|
-
available_media_types << "#{base_media_type}-#{version}+#{extension}"
|
|
115
|
-
available_media_types << "#{base_media_type}-#{version}"
|
|
116
|
-
end
|
|
117
|
-
available_media_types << "#{base_media_type}+#{extension}"
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
available_media_types << base_media_type
|
|
121
|
-
available_media_types.concat(content_types.values.flatten)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
108
|
end
|
|
125
109
|
end
|
|
126
110
|
end
|
|
@@ -22,7 +22,7 @@ module Grape
|
|
|
22
22
|
return if path_info == '/'
|
|
23
23
|
|
|
24
24
|
[mount_path, Grape::Router.normalize_path(prefix)].each do |path|
|
|
25
|
-
path_info.delete_prefix
|
|
25
|
+
path_info = path_info.delete_prefix(path) if path.present? && path != '/' && path_info.start_with?(path)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
slash_position = path_info.index('/', 1) # omit the first one
|