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.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +70 -0
  5. data/.travis.yml +18 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +314 -0
  8. data/CONTRIBUTING.md +118 -0
  9. data/Gemfile +21 -0
  10. data/Guardfile +14 -0
  11. data/LICENSE +20 -0
  12. data/README.md +1777 -0
  13. data/RELEASING.md +105 -0
  14. data/Rakefile +69 -0
  15. data/UPGRADING.md +124 -0
  16. data/grape-security.gemspec +39 -0
  17. data/grape.png +0 -0
  18. data/lib/grape.rb +99 -0
  19. data/lib/grape/api.rb +646 -0
  20. data/lib/grape/cookies.rb +39 -0
  21. data/lib/grape/endpoint.rb +533 -0
  22. data/lib/grape/error_formatter/base.rb +31 -0
  23. data/lib/grape/error_formatter/json.rb +15 -0
  24. data/lib/grape/error_formatter/txt.rb +16 -0
  25. data/lib/grape/error_formatter/xml.rb +15 -0
  26. data/lib/grape/exceptions/base.rb +66 -0
  27. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  28. data/lib/grape/exceptions/invalid_formatter.rb +10 -0
  29. data/lib/grape/exceptions/invalid_versioner_option.rb +10 -0
  30. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +10 -0
  31. data/lib/grape/exceptions/missing_mime_type.rb +10 -0
  32. data/lib/grape/exceptions/missing_option.rb +10 -0
  33. data/lib/grape/exceptions/missing_vendor_option.rb +10 -0
  34. data/lib/grape/exceptions/unknown_options.rb +10 -0
  35. data/lib/grape/exceptions/unknown_validator.rb +10 -0
  36. data/lib/grape/exceptions/validation.rb +26 -0
  37. data/lib/grape/exceptions/validation_errors.rb +43 -0
  38. data/lib/grape/formatter/base.rb +31 -0
  39. data/lib/grape/formatter/json.rb +12 -0
  40. data/lib/grape/formatter/serializable_hash.rb +35 -0
  41. data/lib/grape/formatter/txt.rb +11 -0
  42. data/lib/grape/formatter/xml.rb +12 -0
  43. data/lib/grape/http/request.rb +26 -0
  44. data/lib/grape/locale/en.yml +32 -0
  45. data/lib/grape/middleware/auth/base.rb +30 -0
  46. data/lib/grape/middleware/auth/basic.rb +13 -0
  47. data/lib/grape/middleware/auth/digest.rb +13 -0
  48. data/lib/grape/middleware/auth/oauth2.rb +83 -0
  49. data/lib/grape/middleware/base.rb +62 -0
  50. data/lib/grape/middleware/error.rb +89 -0
  51. data/lib/grape/middleware/filter.rb +17 -0
  52. data/lib/grape/middleware/formatter.rb +150 -0
  53. data/lib/grape/middleware/globals.rb +13 -0
  54. data/lib/grape/middleware/versioner.rb +32 -0
  55. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  56. data/lib/grape/middleware/versioner/header.rb +132 -0
  57. data/lib/grape/middleware/versioner/param.rb +42 -0
  58. data/lib/grape/middleware/versioner/path.rb +52 -0
  59. data/lib/grape/namespace.rb +23 -0
  60. data/lib/grape/parser/base.rb +29 -0
  61. data/lib/grape/parser/json.rb +11 -0
  62. data/lib/grape/parser/xml.rb +11 -0
  63. data/lib/grape/path.rb +70 -0
  64. data/lib/grape/route.rb +27 -0
  65. data/lib/grape/util/content_types.rb +18 -0
  66. data/lib/grape/util/deep_merge.rb +23 -0
  67. data/lib/grape/util/hash_stack.rb +120 -0
  68. data/lib/grape/validations.rb +322 -0
  69. data/lib/grape/validations/coerce.rb +63 -0
  70. data/lib/grape/validations/default.rb +25 -0
  71. data/lib/grape/validations/exactly_one_of.rb +26 -0
  72. data/lib/grape/validations/mutual_exclusion.rb +25 -0
  73. data/lib/grape/validations/presence.rb +16 -0
  74. data/lib/grape/validations/regexp.rb +12 -0
  75. data/lib/grape/validations/values.rb +23 -0
  76. data/lib/grape/version.rb +3 -0
  77. data/spec/grape/api_spec.rb +2571 -0
  78. data/spec/grape/endpoint_spec.rb +784 -0
  79. data/spec/grape/entity_spec.rb +324 -0
  80. data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
  81. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
  82. data/spec/grape/exceptions/missing_mime_type_spec.rb +18 -0
  83. data/spec/grape/exceptions/missing_option_spec.rb +18 -0
  84. data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
  85. data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
  86. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  87. data/spec/grape/middleware/auth/basic_spec.rb +31 -0
  88. data/spec/grape/middleware/auth/digest_spec.rb +47 -0
  89. data/spec/grape/middleware/auth/oauth2_spec.rb +135 -0
  90. data/spec/grape/middleware/base_spec.rb +58 -0
  91. data/spec/grape/middleware/error_spec.rb +45 -0
  92. data/spec/grape/middleware/exception_spec.rb +184 -0
  93. data/spec/grape/middleware/formatter_spec.rb +258 -0
  94. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  95. data/spec/grape/middleware/versioner/header_spec.rb +302 -0
  96. data/spec/grape/middleware/versioner/param_spec.rb +58 -0
  97. data/spec/grape/middleware/versioner/path_spec.rb +44 -0
  98. data/spec/grape/middleware/versioner_spec.rb +22 -0
  99. data/spec/grape/path_spec.rb +229 -0
  100. data/spec/grape/util/hash_stack_spec.rb +132 -0
  101. data/spec/grape/validations/coerce_spec.rb +208 -0
  102. data/spec/grape/validations/default_spec.rb +123 -0
  103. data/spec/grape/validations/exactly_one_of_spec.rb +71 -0
  104. data/spec/grape/validations/mutual_exclusion_spec.rb +61 -0
  105. data/spec/grape/validations/presence_spec.rb +142 -0
  106. data/spec/grape/validations/regexp_spec.rb +40 -0
  107. data/spec/grape/validations/values_spec.rb +152 -0
  108. data/spec/grape/validations/zh-CN.yml +10 -0
  109. data/spec/grape/validations_spec.rb +994 -0
  110. data/spec/shared/versioning_examples.rb +121 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/basic_auth_encode_helpers.rb +3 -0
  113. data/spec/support/content_type_helpers.rb +11 -0
  114. data/spec/support/versioned_helpers.rb +50 -0
  115. 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