lib/grape/api.rb in grape-0.6.1 vs lib/grape/api.rb in grape-0.7.0
- old
+ new
@@ -7,10 +7,12 @@
class << self
attr_reader :endpoints, :instance, :routes, :route_set, :settings, :versions
attr_writer :logger
+ LOCK = Mutex.new
+
def logger(logger = nil)
if logger
@logger = logger
else
@logger ||= Logger.new($stdout)
@@ -24,19 +26,19 @@
@routes = nil
reset_validations!
end
def compile
- @instance = new
+ @instance ||= new
end
def change!
@instance = nil
end
def call(env)
- compile unless instance
+ LOCK.synchronize { compile } unless instance
call!(env)
end
def call!(env)
instance.call(env)
@@ -197,10 +199,11 @@
# @param [Array] exception_classes A list of classes that you want to rescue, or
# the symbol :all to rescue from all exceptions.
# @param [Block] block Execution block to handle the given exception.
# @param [Hash] options Options for the rescue usage.
# @option options [Boolean] :backtrace Include a backtrace in the rescue response.
+ # @option options [Boolean] :rescue_subclasses Also rescue subclasses of exception classes
# @param [Proc] handler Execution proc to handle the given exception as an
# alternative to passing a block
def rescue_from(*args, &block)
if args.last.is_a?(Proc)
handler = args.pop
@@ -209,23 +212,16 @@
end
options = args.last.is_a?(Hash) ? args.pop : {}
handler ||= proc { options[:with] } if options.has_key?(:with)
- if handler
- args.each do |arg|
- imbue(:rescue_handlers, { arg => handler })
- end
- end
+ handler_type = !!options[:rescue_subclasses] ? :rescue_handlers : :base_only_rescue_handlers
+ imbue handler_type, Hash[args.map { |arg| [arg, handler] }]
imbue(:rescue_options, options)
- if args.include?(:all)
- set(:rescue_all, true)
- else
- imbue(:rescued_errors, args)
- end
+ set(:rescue_all, true) if args.include?(:all)
end
# Allows you to specify a default representation entity for a
# class. This allows you to map your models to their respective
# entities once and then simply call `present` with the model.
@@ -272,15 +268,20 @@
#
def helpers(new_mod = nil, &block)
if block_given? || new_mod
mod = settings.peek[:helpers] || Module.new
if new_mod
+ inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(Helpers)
mod.class_eval do
include new_mod
end
end
- mod.class_eval(&block) if block_given?
+ if block_given?
+ inject_api_helpers_to_mod(mod) do
+ mod.class_eval(&block)
+ end
+ end
set(:helpers, mod)
else
mod = Module.new
settings.stack.each do |s|
mod.send :include, s[:helpers] if s[:helpers]
@@ -322,15 +323,16 @@
app_settings = settings.clone
mount_path = Rack::Mount::Utils.normalize_path([settings[:mount_path], path].compact.join("/"))
app_settings.set :mount_path, mount_path
app.inherit_settings(app_settings)
end
- endpoints << Grape::Endpoint.new(settings.clone, {
+ endpoints << Grape::Endpoint.new(
+ settings.clone,
method: :any,
path: path,
app: app
- })
+ )
end
end
# Defines a route that will be recognized
# by the Grape API.
@@ -358,10 +360,14 @@
def before(&block)
imbue(:befores, [block])
end
+ def before_validation(&block)
+ imbue(:before_validations, [block])
+ end
+
def after_validation(&block)
imbue(:after_validations, [block])
end
def after(&block)
@@ -508,18 +514,24 @@
endpoints.each do |e|
e.settings.prepend(other_stack)
e.options[:app].inherit_settings(other_stack) if e.options[:app].respond_to?(:inherit_settings, true)
end
end
+
+ def inject_api_helpers_to_mod(mod, &block)
+ mod.extend(Helpers)
+ yield if block_given?
+ mod.api_changed(self)
+ end
end
def initialize
@route_set = Rack::Mount::RouteSet.new
+ add_head_not_allowed_methods_and_options_methods
self.class.endpoints.each do |endpoint|
endpoint.mount_in(@route_set)
end
- add_head_not_allowed_methods
@route_set.freeze
end
def call(env)
status, headers, body = @route_set.call(env)
@@ -547,41 +559,80 @@
# For every resource add a 'OPTIONS' route that returns an HTTP 204 response
# with a list of HTTP methods that can be called. Also add a route that
# will return an HTTP 405 response for any HTTP method that the resource
# cannot handle.
- def add_head_not_allowed_methods
- allowed_methods = Hash.new { |h, k| h[k] = [] }
- resources = self.class.endpoints.map do |endpoint|
- if endpoint.options[:app] && endpoint.options[:app].respond_to?(:endpoints)
- endpoint.options[:app].endpoints.map(&:routes)
- else
- endpoint.routes
+ def add_head_not_allowed_methods_and_options_methods
+ methods_per_path = {}
+ self.class.endpoints.each do |endpoint|
+ routes = endpoint.routes
+ routes.each do |route|
+ methods_per_path[route.route_path] ||= []
+ methods_per_path[route.route_path] << route.route_method
end
end
- resources.flatten.each do |route|
- allowed_methods[route.route_compiled] << route.route_method
- end
- allowed_methods.each do |path_info, methods|
- if methods.include?('GET') && !methods.include?("HEAD") && !self.class.settings[:do_not_route_head]
- methods = methods | ['HEAD']
+
+ # The paths we collected are prepared (cf. Path#prepare), so they
+ # contain already versioning information when using path versioning.
+ # Disable versioning so adding a route won't prepend versioning
+ # informations again.
+ without_versioning do
+ methods_per_path.each do |path, methods|
+ allowed_methods = methods.dup
+ unless self.class.settings[:do_not_route_head]
+ if allowed_methods.include?('GET')
+ allowed_methods = allowed_methods | ['HEAD']
+ end
+ end
+
+ allow_header = (['OPTIONS'] | allowed_methods).join(', ')
+ unless self.class.settings[:do_not_route_options]
+ unless allowed_methods.include?('OPTIONS')
+ self.class.options(path, {}) do
+ header 'Allow', allow_header
+ status 204
+ ''
+ end
+ end
+ end
+
+ not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
+ not_allowed_methods << 'OPTIONS' if self.class.settings[:do_not_route_options]
+ self.class.route(not_allowed_methods, path) do
+ header 'Allow', allow_header
+ status 405
+ ''
+ end
end
- allow_header = (["OPTIONS"] | methods).join(", ")
- unless methods.include?("OPTIONS") || self.class.settings[:do_not_route_options]
- @route_set.add_route(proc { [204, { 'Allow' => allow_header }, []] }, {
- path_info: path_info,
- request_method: "OPTIONS"
- })
- end
- not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - methods
- not_allowed_methods << "OPTIONS" if self.class.settings[:do_not_route_options]
- not_allowed_methods.each do |bad_method|
- @route_set.add_route(proc { [405, { 'Allow' => allow_header, 'Content-Type' => 'text/plain' }, []] }, {
- path_info: path_info,
- request_method: bad_method
- })
- end
end
end
+ def without_versioning(&block)
+ self.class.settings.push(version: nil, version_options: nil)
+ yield
+ self.class.settings.pop
+ end
+
+ # This module extends user defined helpers
+ # to provide some API-specific functionality
+ module Helpers
+ attr_accessor :api
+ def params(name, &block)
+ @named_params ||= {}
+ @named_params.merge! name => block
+ end
+
+ def api_changed(new_api)
+ @api = new_api
+ process_named_params
+ end
+
+ protected
+
+ def process_named_params
+ if @named_params && @named_params.any?
+ api.imbue(:named_params, @named_params)
+ end
+ end
+ end
end
end