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