grape-security 0.8.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 +7 -0
- data/.gitignore +45 -0
- data/.rspec +2 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +18 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +314 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +21 -0
- data/Guardfile +14 -0
- data/LICENSE +20 -0
- data/README.md +1777 -0
- data/RELEASING.md +105 -0
- data/Rakefile +69 -0
- data/UPGRADING.md +124 -0
- data/grape-security.gemspec +39 -0
- data/grape.png +0 -0
- data/lib/grape.rb +99 -0
- data/lib/grape/api.rb +646 -0
- data/lib/grape/cookies.rb +39 -0
- data/lib/grape/endpoint.rb +533 -0
- data/lib/grape/error_formatter/base.rb +31 -0
- data/lib/grape/error_formatter/json.rb +15 -0
- data/lib/grape/error_formatter/txt.rb +16 -0
- data/lib/grape/error_formatter/xml.rb +15 -0
- data/lib/grape/exceptions/base.rb +66 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +10 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
- data/lib/grape/exceptions/missing_mime_type.rb +10 -0
- data/lib/grape/exceptions/missing_option.rb +10 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
- data/lib/grape/exceptions/unknown_options.rb +10 -0
- data/lib/grape/exceptions/unknown_validator.rb +10 -0
- data/lib/grape/exceptions/validation.rb +26 -0
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +31 -0
- data/lib/grape/formatter/json.rb +12 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +12 -0
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +32 -0
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +13 -0
- data/lib/grape/middleware/auth/digest.rb +13 -0
- data/lib/grape/middleware/auth/oauth2.rb +83 -0
- data/lib/grape/middleware/base.rb +62 -0
- data/lib/grape/middleware/error.rb +89 -0
- data/lib/grape/middleware/filter.rb +17 -0
- data/lib/grape/middleware/formatter.rb +150 -0
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner.rb +32 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +132 -0
- data/lib/grape/middleware/versioner/param.rb +42 -0
- data/lib/grape/middleware/versioner/path.rb +52 -0
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +29 -0
- data/lib/grape/parser/json.rb +11 -0
- data/lib/grape/parser/xml.rb +11 -0
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +27 -0
- data/lib/grape/util/content_types.rb +18 -0
- data/lib/grape/util/deep_merge.rb +23 -0
- data/lib/grape/util/hash_stack.rb +120 -0
- data/lib/grape/validations.rb +322 -0
- data/lib/grape/validations/coerce.rb +63 -0
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/exactly_one_of.rb +26 -0
- data/lib/grape/validations/mutual_exclusion.rb +25 -0
- data/lib/grape/validations/presence.rb +16 -0
- data/lib/grape/validations/regexp.rb +12 -0
- data/lib/grape/validations/values.rb +23 -0
- data/lib/grape/version.rb +3 -0
- data/spec/grape/api_spec.rb +2571 -0
- data/spec/grape/endpoint_spec.rb +784 -0
- data/spec/grape/entity_spec.rb +324 -0
- data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
- data/spec/grape/exceptions/missing_option_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +31 -0
- data/spec/grape/middleware/auth/digest_spec.rb +47 -0
- data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
- data/spec/grape/middleware/base_spec.rb +58 -0
- data/spec/grape/middleware/error_spec.rb +45 -0
- data/spec/grape/middleware/exception_spec.rb +184 -0
- data/spec/grape/middleware/formatter_spec.rb +258 -0
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +302 -0
- data/spec/grape/middleware/versioner/param_spec.rb +58 -0
- data/spec/grape/middleware/versioner/path_spec.rb +44 -0
- data/spec/grape/middleware/versioner_spec.rb +22 -0
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +132 -0
- data/spec/grape/validations/coerce_spec.rb +208 -0
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
- data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
- data/spec/grape/validations/presence_spec.rb +142 -0
- data/spec/grape/validations/regexp_spec.rb +40 -0
- data/spec/grape/validations/values_spec.rb +152 -0
- data/spec/grape/validations/zh-CN.yml +10 -0
- data/spec/grape/validations_spec.rb +994 -0
- data/spec/shared/versioning_examples.rb +121 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/basic_auth_encode_helpers.rb +3 -0
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +50 -0
- metadata +421 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module Grape
|
2
|
+
class Cookies
|
3
|
+
def initialize
|
4
|
+
@cookies = {}
|
5
|
+
@send_cookies = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def read(request)
|
9
|
+
request.cookies.each do |name, value|
|
10
|
+
@cookies[name.to_s] = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(header)
|
15
|
+
@cookies.select { |key, value| @send_cookies[key] == true }.each do |name, value|
|
16
|
+
cookie_value = value.is_a?(Hash) ? value : { value: value }
|
17
|
+
Rack::Utils.set_cookie_header! header, name, cookie_value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](name)
|
22
|
+
@cookies[name.to_s]
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(name, value)
|
26
|
+
@cookies[name.to_s] = value
|
27
|
+
@send_cookies[name.to_s] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def each(&block)
|
31
|
+
@cookies.each(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(name, opts = {})
|
35
|
+
options = opts.merge(value: 'deleted', expires: Time.at(0))
|
36
|
+
self.[]=(name, options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,533 @@
|
|
1
|
+
module Grape
|
2
|
+
# An Endpoint is the proxy scope in which all routing
|
3
|
+
# blocks are executed. In other words, any methods
|
4
|
+
# on the instance level of this class may be called
|
5
|
+
# from inside a `get`, `post`, etc.
|
6
|
+
class Endpoint
|
7
|
+
attr_accessor :block, :source, :options, :settings
|
8
|
+
attr_reader :env, :request, :headers, :params
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def before_each(new_setup = false, &block)
|
12
|
+
if new_setup == false
|
13
|
+
if block_given?
|
14
|
+
@before_each = block
|
15
|
+
else
|
16
|
+
return @before_each
|
17
|
+
end
|
18
|
+
else
|
19
|
+
@before_each = new_setup
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
#
|
25
|
+
# Create an UnboundMethod that is appropriate for executing an endpoint
|
26
|
+
# route.
|
27
|
+
#
|
28
|
+
# The unbound method allows explicit calls to +return+ without raising a
|
29
|
+
# +LocalJumpError+. The method will be removed, but a +Proc+ reference to
|
30
|
+
# it will be returned. The returned +Proc+ expects a single argument: the
|
31
|
+
# instance of +Endpoint+ to bind to the method during the call.
|
32
|
+
#
|
33
|
+
# @param [String, Symbol] method_name
|
34
|
+
# @return [Proc]
|
35
|
+
# @raise [NameError] an instance method with the same name already exists
|
36
|
+
def generate_api_method(method_name, &block)
|
37
|
+
if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s)
|
38
|
+
raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name")
|
39
|
+
end
|
40
|
+
define_method(method_name, &block)
|
41
|
+
method = instance_method(method_name)
|
42
|
+
remove_method(method_name)
|
43
|
+
proc { |endpoint_instance| method.bind(endpoint_instance).call }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(settings, options = {}, &block)
|
48
|
+
require_option(options, :path)
|
49
|
+
require_option(options, :method)
|
50
|
+
|
51
|
+
@settings = settings
|
52
|
+
@options = options
|
53
|
+
|
54
|
+
@options[:path] = Array(options[:path])
|
55
|
+
@options[:path] << '/' if options[:path].empty?
|
56
|
+
|
57
|
+
@options[:method] = Array(options[:method])
|
58
|
+
@options[:route_options] ||= {}
|
59
|
+
|
60
|
+
if block_given?
|
61
|
+
@source = block
|
62
|
+
@block = self.class.generate_api_method(method_name, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def require_option(options, key)
|
67
|
+
raise Grape::Exceptions::MissingOption.new(key) unless options.key?(key)
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_name
|
71
|
+
[options[:method],
|
72
|
+
Namespace.joined_space(settings),
|
73
|
+
settings.gather(:mount_path).join('/'),
|
74
|
+
options[:path].join('/')
|
75
|
+
].join(" ")
|
76
|
+
end
|
77
|
+
|
78
|
+
def routes
|
79
|
+
@routes ||= endpoints ? endpoints.collect(&:routes).flatten : prepare_routes
|
80
|
+
end
|
81
|
+
|
82
|
+
def mount_in(route_set)
|
83
|
+
if endpoints
|
84
|
+
endpoints.each { |e| e.mount_in(route_set) }
|
85
|
+
else
|
86
|
+
routes.each do |route|
|
87
|
+
methods = [route.route_method]
|
88
|
+
if !settings[:do_not_route_head] && route.route_method == "GET"
|
89
|
+
methods << "HEAD"
|
90
|
+
end
|
91
|
+
methods.each do |method|
|
92
|
+
route_set.add_route(self, {
|
93
|
+
path_info: route.route_compiled,
|
94
|
+
request_method: method
|
95
|
+
}, route_info: route)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def prepare_routes
|
102
|
+
routes = []
|
103
|
+
options[:method].each do |method|
|
104
|
+
options[:path].each do |path|
|
105
|
+
prepared_path = prepare_path(path)
|
106
|
+
|
107
|
+
anchor = options[:route_options][:anchor]
|
108
|
+
anchor = anchor.nil? ? true : anchor
|
109
|
+
|
110
|
+
endpoint_requirements = options[:route_options][:requirements] || {}
|
111
|
+
all_requirements = (settings.gather(:namespace).map(&:requirements) << endpoint_requirements)
|
112
|
+
requirements = all_requirements.reduce({}) do |base_requirements, single_requirements|
|
113
|
+
base_requirements.merge!(single_requirements)
|
114
|
+
end
|
115
|
+
|
116
|
+
path = compile_path(prepared_path, anchor && !options[:app], requirements)
|
117
|
+
regex = Rack::Mount::RegexpWithNamedGroups.new(path)
|
118
|
+
path_params = {}
|
119
|
+
# named parameters in the api path
|
120
|
+
named_params = regex.named_captures.map { |nc| nc[0] } - %w(version format)
|
121
|
+
named_params.each { |named_param| path_params[named_param] = "" }
|
122
|
+
# route parameters declared via desc or appended to the api declaration
|
123
|
+
route_params = (options[:route_options][:params] || {})
|
124
|
+
path_params.merge!(route_params)
|
125
|
+
request_method = (method.to_s.upcase unless method == :any)
|
126
|
+
routes << Route.new(options[:route_options].clone.merge(
|
127
|
+
prefix: settings[:root_prefix],
|
128
|
+
version: settings[:version] ? settings[:version].join('|') : nil,
|
129
|
+
namespace: namespace,
|
130
|
+
method: request_method,
|
131
|
+
path: prepared_path,
|
132
|
+
params: path_params,
|
133
|
+
compiled: path
|
134
|
+
))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
routes
|
138
|
+
end
|
139
|
+
|
140
|
+
def prepare_path(path)
|
141
|
+
Path.prepare(path, namespace, settings)
|
142
|
+
end
|
143
|
+
|
144
|
+
def namespace
|
145
|
+
@namespace ||= Namespace.joined_space_path(settings)
|
146
|
+
end
|
147
|
+
|
148
|
+
def compile_path(prepared_path, anchor = true, requirements = {})
|
149
|
+
endpoint_options = {}
|
150
|
+
endpoint_options[:version] = /#{settings[:version].join('|')}/ if settings[:version]
|
151
|
+
endpoint_options.merge!(requirements)
|
152
|
+
Rack::Mount::Strexp.compile(prepared_path, endpoint_options, %w( / . ? ), anchor)
|
153
|
+
end
|
154
|
+
|
155
|
+
def call(env)
|
156
|
+
dup.call!(env)
|
157
|
+
end
|
158
|
+
|
159
|
+
def call!(env)
|
160
|
+
extend helpers
|
161
|
+
|
162
|
+
env['api.endpoint'] = self
|
163
|
+
if options[:app]
|
164
|
+
options[:app].call(env)
|
165
|
+
else
|
166
|
+
builder = build_middleware
|
167
|
+
builder.run options[:app] || lambda { |arg| run(arg) }
|
168
|
+
builder.call(env)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# A filtering method that will return a hash
|
173
|
+
# consisting only of keys that have been declared by a
|
174
|
+
# `params` statement against the current/target endpoint or parent
|
175
|
+
# namespaces
|
176
|
+
#
|
177
|
+
# @param params [Hash] The initial hash to filter. Usually this will just be `params`
|
178
|
+
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
|
179
|
+
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
|
180
|
+
# you want only to return params declared against the current/target endpoint
|
181
|
+
def declared(params, options = {}, declared_params = nil)
|
182
|
+
options[:include_missing] = true unless options.key?(:include_missing)
|
183
|
+
options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
|
184
|
+
if declared_params.nil?
|
185
|
+
declared_params = !options[:include_parent_namespaces] ? settings[:declared_params] :
|
186
|
+
settings.gather(:declared_params)
|
187
|
+
end
|
188
|
+
|
189
|
+
unless declared_params
|
190
|
+
raise ArgumentError, "Tried to filter for declared parameters but none exist."
|
191
|
+
end
|
192
|
+
|
193
|
+
if params.is_a? Array
|
194
|
+
params.map do |param|
|
195
|
+
declared(param || {}, options, declared_params)
|
196
|
+
end
|
197
|
+
else
|
198
|
+
declared_params.inject({}) do |hash, key|
|
199
|
+
key = { key => nil } unless key.is_a? Hash
|
200
|
+
|
201
|
+
key.each_pair do |parent, children|
|
202
|
+
output_key = options[:stringify] ? parent.to_s : parent.to_sym
|
203
|
+
if params.key?(parent) || options[:include_missing]
|
204
|
+
hash[output_key] = if children
|
205
|
+
declared(params[parent] || {}, options, Array(children))
|
206
|
+
else
|
207
|
+
params[parent]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
hash
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# The API version as specified in the URL.
|
218
|
+
def version
|
219
|
+
env['api.version']
|
220
|
+
end
|
221
|
+
|
222
|
+
# End the request and display an error to the
|
223
|
+
# end user with the specified message.
|
224
|
+
#
|
225
|
+
# @param message [String] The message to display.
|
226
|
+
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
|
227
|
+
def error!(message, status = nil, headers = nil)
|
228
|
+
status = settings[:default_error_status] unless status
|
229
|
+
throw :error, message: message, status: status, headers: headers
|
230
|
+
end
|
231
|
+
|
232
|
+
# Redirect to a new url.
|
233
|
+
#
|
234
|
+
# @param url [String] The url to be redirect.
|
235
|
+
# @param options [Hash] The options used when redirect.
|
236
|
+
# :permanent, default true.
|
237
|
+
def redirect(url, options = {})
|
238
|
+
merged_options = { permanent: false }.merge(options)
|
239
|
+
if merged_options[:permanent]
|
240
|
+
status 301
|
241
|
+
else
|
242
|
+
if env['HTTP_VERSION'] == 'HTTP/1.1' && request.request_method.to_s.upcase != "GET"
|
243
|
+
status 303
|
244
|
+
else
|
245
|
+
status 302
|
246
|
+
end
|
247
|
+
end
|
248
|
+
header "Location", url
|
249
|
+
body ""
|
250
|
+
end
|
251
|
+
|
252
|
+
# Set or retrieve the HTTP status code.
|
253
|
+
#
|
254
|
+
# @param status [Integer] The HTTP Status Code to return for this request.
|
255
|
+
def status(status = nil)
|
256
|
+
if status
|
257
|
+
@status = status
|
258
|
+
else
|
259
|
+
return @status if @status
|
260
|
+
case request.request_method.to_s.upcase
|
261
|
+
when 'POST'
|
262
|
+
201
|
263
|
+
else
|
264
|
+
200
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Set an individual header or retrieve
|
270
|
+
# all headers that have been set.
|
271
|
+
def header(key = nil, val = nil)
|
272
|
+
if key
|
273
|
+
val ? @header[key.to_s] = val : @header.delete(key.to_s)
|
274
|
+
else
|
275
|
+
@header
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Set response content-type
|
280
|
+
def content_type(val)
|
281
|
+
header('Content-Type', val)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Set or get a cookie
|
285
|
+
#
|
286
|
+
# @example
|
287
|
+
# cookies[:mycookie] = 'mycookie val'
|
288
|
+
# cookies['mycookie-string'] = 'mycookie string val'
|
289
|
+
# cookies[:more] = { value: '123', expires: Time.at(0) }
|
290
|
+
# cookies.delete :more
|
291
|
+
#
|
292
|
+
def cookies
|
293
|
+
@cookies ||= Cookies.new
|
294
|
+
end
|
295
|
+
|
296
|
+
# Allows you to define the response body as something other than the
|
297
|
+
# return value.
|
298
|
+
#
|
299
|
+
# @example
|
300
|
+
# get '/body' do
|
301
|
+
# body "Body"
|
302
|
+
# "Not the Body"
|
303
|
+
# end
|
304
|
+
#
|
305
|
+
# GET /body # => "Body"
|
306
|
+
def body(value = nil)
|
307
|
+
if value
|
308
|
+
@body = value
|
309
|
+
else
|
310
|
+
@body
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Allows you to make use of Grape Entities by setting
|
315
|
+
# the response body to the serializable hash of the
|
316
|
+
# entity provided in the `:with` option. This has the
|
317
|
+
# added benefit of automatically passing along environment
|
318
|
+
# and version information to the serialization, making it
|
319
|
+
# very easy to do conditional exposures. See Entity docs
|
320
|
+
# for more info.
|
321
|
+
#
|
322
|
+
# @example
|
323
|
+
#
|
324
|
+
# get '/users/:id' do
|
325
|
+
# present User.find(params[:id]),
|
326
|
+
# with: API::Entities::User,
|
327
|
+
# admin: current_user.admin?
|
328
|
+
# end
|
329
|
+
def present(*args)
|
330
|
+
options = args.count > 1 ? args.extract_options! : {}
|
331
|
+
key, object = if args.count == 2 && args.first.is_a?(Symbol)
|
332
|
+
args
|
333
|
+
else
|
334
|
+
[nil, args.first]
|
335
|
+
end
|
336
|
+
entity_class = options.delete(:with)
|
337
|
+
|
338
|
+
if entity_class.nil?
|
339
|
+
# entity class not explicitely defined, auto-detect from relation#klass or first object in the collection
|
340
|
+
object_class = if object.respond_to?(:klass)
|
341
|
+
object.klass
|
342
|
+
else
|
343
|
+
object.respond_to?(:first) ? object.first.class : object.class
|
344
|
+
end
|
345
|
+
|
346
|
+
object_class.ancestors.each do |potential|
|
347
|
+
entity_class ||= (settings[:representations] || {})[potential]
|
348
|
+
end
|
349
|
+
|
350
|
+
entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
|
351
|
+
end
|
352
|
+
|
353
|
+
root = options.delete(:root)
|
354
|
+
|
355
|
+
representation = if entity_class
|
356
|
+
embeds = { env: env }
|
357
|
+
embeds[:version] = env['api.version'] if env['api.version']
|
358
|
+
entity_class.represent(object, embeds.merge(options))
|
359
|
+
else
|
360
|
+
object
|
361
|
+
end
|
362
|
+
|
363
|
+
representation = { root => representation } if root
|
364
|
+
representation = (@body || {}).merge(key => representation) if key
|
365
|
+
body representation
|
366
|
+
end
|
367
|
+
|
368
|
+
# Returns route information for the current request.
|
369
|
+
#
|
370
|
+
# @example
|
371
|
+
#
|
372
|
+
# desc "Returns the route description."
|
373
|
+
# get '/' do
|
374
|
+
# route.route_description
|
375
|
+
# end
|
376
|
+
def route
|
377
|
+
env["rack.routing_args"][:route_info]
|
378
|
+
end
|
379
|
+
|
380
|
+
# Return the collection of endpoints within this endpoint.
|
381
|
+
# This is the case when an Grape::API mounts another Grape::API.
|
382
|
+
def endpoints
|
383
|
+
if options[:app] && options[:app].respond_to?(:endpoints)
|
384
|
+
options[:app].endpoints
|
385
|
+
else
|
386
|
+
nil
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
protected
|
391
|
+
|
392
|
+
def run(env)
|
393
|
+
@env = env
|
394
|
+
@header = {}
|
395
|
+
|
396
|
+
@request = Grape::Request.new(env)
|
397
|
+
@params = @request.params
|
398
|
+
@headers = @request.headers
|
399
|
+
|
400
|
+
cookies.read(@request)
|
401
|
+
|
402
|
+
self.class.before_each.call(self) if self.class.before_each
|
403
|
+
|
404
|
+
run_filters befores
|
405
|
+
|
406
|
+
run_filters before_validations
|
407
|
+
|
408
|
+
# Retrieve validations from this namespace and all parent namespaces.
|
409
|
+
validation_errors = []
|
410
|
+
settings.gather(:validations).each do |validator|
|
411
|
+
begin
|
412
|
+
validator.validate!(params)
|
413
|
+
rescue Grape::Exceptions::Validation => e
|
414
|
+
validation_errors << e
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
if validation_errors.any?
|
419
|
+
raise Grape::Exceptions::ValidationErrors, errors: validation_errors
|
420
|
+
end
|
421
|
+
|
422
|
+
run_filters after_validations
|
423
|
+
|
424
|
+
response_text = @block ? @block.call(self) : nil
|
425
|
+
run_filters afters
|
426
|
+
cookies.write(header)
|
427
|
+
|
428
|
+
[status, header, [body || response_text]]
|
429
|
+
end
|
430
|
+
|
431
|
+
def build_middleware
|
432
|
+
b = Rack::Builder.new
|
433
|
+
|
434
|
+
b.use Rack::Head
|
435
|
+
b.use Grape::Middleware::Error,
|
436
|
+
format: settings[:format],
|
437
|
+
content_types: settings[:content_types],
|
438
|
+
default_status: settings[:default_error_status] || 500,
|
439
|
+
rescue_all: settings[:rescue_all],
|
440
|
+
default_error_formatter: settings[:default_error_formatter],
|
441
|
+
error_formatters: settings[:error_formatters],
|
442
|
+
rescue_options: settings[:rescue_options],
|
443
|
+
rescue_handlers: merged_setting(:rescue_handlers),
|
444
|
+
base_only_rescue_handlers: merged_setting(:base_only_rescue_handlers),
|
445
|
+
all_rescue_handler: settings[:all_rescue_handler]
|
446
|
+
|
447
|
+
aggregate_setting(:middleware).each do |m|
|
448
|
+
m = m.dup
|
449
|
+
block = m.pop if m.last.is_a?(Proc)
|
450
|
+
if block
|
451
|
+
b.use(*m, &block)
|
452
|
+
else
|
453
|
+
b.use(*m)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
if settings[:auth]
|
458
|
+
auth_proc = settings[:auth][:proc]
|
459
|
+
auth_proc_context = self
|
460
|
+
auth_middleware = {
|
461
|
+
http_basic: { class: Rack::Auth::Basic, args: [settings[:auth][:realm]] },
|
462
|
+
http_digest: { class: Rack::Auth::Digest::MD5, args: [settings[:auth][:realm], settings[:auth][:opaque]] }
|
463
|
+
}[settings[:auth][:type]]
|
464
|
+
|
465
|
+
# evaluate auth proc in context of endpoint
|
466
|
+
if auth_middleware
|
467
|
+
b.use auth_middleware[:class], *auth_middleware[:args] do |*args|
|
468
|
+
auth_proc_context.instance_exec(*args, &auth_proc)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
if settings[:version]
|
474
|
+
b.use Grape::Middleware::Versioner.using(settings[:version_options][:using]),
|
475
|
+
versions: settings[:version] ? settings[:version].flatten : nil,
|
476
|
+
version_options: settings[:version_options],
|
477
|
+
prefix: settings[:root_prefix]
|
478
|
+
|
479
|
+
end
|
480
|
+
|
481
|
+
b.use Grape::Middleware::Formatter,
|
482
|
+
format: settings[:format],
|
483
|
+
default_format: settings[:default_format] || :txt,
|
484
|
+
content_types: settings[:content_types],
|
485
|
+
formatters: settings[:formatters],
|
486
|
+
parsers: settings[:parsers]
|
487
|
+
|
488
|
+
b
|
489
|
+
end
|
490
|
+
|
491
|
+
def helpers
|
492
|
+
m = Module.new
|
493
|
+
settings.stack.each do |frame|
|
494
|
+
m.send :include, frame[:helpers] if frame[:helpers]
|
495
|
+
end
|
496
|
+
m
|
497
|
+
end
|
498
|
+
|
499
|
+
def aggregate_setting(key)
|
500
|
+
settings.stack.inject([]) do |aggregate, frame|
|
501
|
+
aggregate + (frame[key] || [])
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def merged_setting(key)
|
506
|
+
settings.stack.inject({}) do |merged, frame|
|
507
|
+
merged.merge(frame[key] || {})
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def run_filters(filters)
|
512
|
+
(filters || []).each do |filter|
|
513
|
+
instance_eval(&filter)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def befores
|
518
|
+
aggregate_setting(:befores)
|
519
|
+
end
|
520
|
+
|
521
|
+
def before_validations
|
522
|
+
aggregate_setting(:before_validations)
|
523
|
+
end
|
524
|
+
|
525
|
+
def after_validations
|
526
|
+
aggregate_setting(:after_validations)
|
527
|
+
end
|
528
|
+
|
529
|
+
def afters
|
530
|
+
aggregate_setting(:afters)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|