lib/grape/api.rb in grape-0.2.6 vs lib/grape/api.rb in grape-0.3.0

- old
+ new

@@ -1,12 +1,5 @@ -require 'rack/mount' -require 'rack/auth/basic' -require 'rack/auth/digest/md5' -require 'logger' -require 'grape/util/deep_merge' -require 'grape/util/content_types' - module Grape # The API class is the primary entry point for # creating Grape APIs.Users should subclass this # class in order to build an API. class API @@ -71,16 +64,25 @@ # @param value [Object] The value to which to set the configuration variable. def imbue(key, value) settings.imbue(key, value) end - # Define a root URL prefix for your entire - # API. + # Define a root URL prefix for your entire API. def prefix(prefix = nil) prefix ? set(:root_prefix, prefix) : settings[:root_prefix] end + # Do not route HEAD requests to GET requests automatically + def do_not_route_head! + set(:do_not_route_head, true) + end + + # Do not automatically route OPTIONS + def do_not_route_options! + set(:do_not_route_options, true) + end + # Specify an API version. # # @example API with legacy support. # class MyAPI < Grape::API # version 'v2' @@ -100,11 +102,11 @@ if args.any? options = args.pop if args.last.is_a? Hash options ||= {} options = {:using => :path}.merge!(options) - raise ArgumentError, "Must specify :vendor option." if options[:using] == :header && !options.has_key?(:vendor) + raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.has_key?(:vendor) @versions = versions | args nest(block) do set(:version, args) set(:version_options, options) @@ -132,11 +134,11 @@ set(:format, new_format.to_sym) # define the default error formatters set(:default_error_formatter, Grape::ErrorFormatter::Base.formatter_for(new_format, {})) # define a single mime type mime_type = content_types[new_format.to_sym] - raise "missing mime type for #{new_format}" unless mime_type + raise Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type settings.imbue(:content_types, new_format.to_sym => mime_type) else settings[:format] end end @@ -223,11 +225,11 @@ # bubble up and utilize the entity provided on that `represent` call. # # @param model_class [Class] The model class that will be represented. # @option options [Class] :with The entity class that will represent the model. def represent(model_class, options) - raise ArgumentError, "You must specify an entity class in the :with option." unless options[:with] && options[:with].is_a?(Class) + raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with] && options[:with].is_a?(Class) imbue(:representations, model_class => options[:with]) end # Add helper methods that will be accessible from any # endpoint within this namespace (and child namespaces). @@ -292,20 +294,23 @@ options[:opaque] ||= "secret" auth :http_digest, options, &block end def mount(mounts) - mounts = {mounts => '/'} unless mounts.respond_to?(:each_pair) + mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:inherit_settings) - app.inherit_settings(settings.clone) + 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. @@ -429,11 +434,11 @@ else instance_eval &block end end - def inherited(subclass) + def inherited(subclass) subclass.reset! subclass.logger = logger.clone end def inherit_settings(other_stack) @@ -450,13 +455,28 @@ add_head_not_allowed_methods @route_set.freeze end def call(env) - @route_set.call(env) + status, headers, body = @route_set.call(env) + headers.delete('X-Cascade') unless cascade? + [ status, headers, body ] end + # Some requests may return a HTTP 404 error if grape cannot find a matching + # route. In this case, Rack::Mount adds a X-Cascade header to the response + # and sets it to 'pass', indicating to grape's parents they should keep + # looking for a matching route on other resources. + # + # In some applications (e.g. mounting grape on rails), one might need to trap + # errors from reaching upstream. This is effectivelly done by unsetting + # X-Cascade. Default :cascade is true. + def cascade? + cascade = ((self.class.settings || {})[:version_options] || {})[:cascade] + cascade.nil? ? true : cascade + end + reset! private # For every resource add a 'OPTIONS' route that returns an HTTP 204 response @@ -471,19 +491,22 @@ endpoint.routes 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' ] + end allow_header = (["OPTIONS"] | methods).join(", ") - unless methods.include?("OPTIONS") + 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 }, []]}, { :path_info => path_info, :request_method => bad_method })