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
})