grape-security 0.8.0

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