lib/grape/api.rb in grape-0.2.1.1 vs lib/grape/api.rb in grape-0.2.2
- old
+ new
@@ -1,15 +1,18 @@
require 'rack/mount'
require 'rack/auth/basic'
require 'rack/auth/digest/md5'
require 'logger'
+require 'grape/util/deep_merge'
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
+ extend Validations::ClassMethods
+
class << self
attr_reader :route_set
attr_reader :versions
attr_reader :routes
attr_reader :settings
@@ -30,10 +33,11 @@
@settings = Grape::Util::HashStack.new
@route_set = Rack::Mount::RouteSet.new
@endpoints = []
@mountings = []
@routes = nil
+ reset_validations!
end
def compile
@instance = self.new
end
@@ -94,16 +98,21 @@
def version(*args, &block)
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)
+
@versions = versions | args
nest(block) do
set(:version, args)
set(:version_options, options)
end
end
+
+ @versions.last unless @versions.nil?
end
# Add a description to the next namespace or function.
def desc(description, options = {})
@last_description = options.merge(:description => description)
@@ -118,11 +127,11 @@
# Specify the format for the API's serializers.
# May be `:json` or `:txt`.
def format(new_format = nil)
new_format ? set(:format, new_format.to_sym) : settings[:format]
end
-
+
# Specify the format for error messages.
# May be `:json` or `:txt` (default).
def error_format(new_format = nil)
new_format ? set(:error_format, new_format.to_sym) : settings[:error_format]
end
@@ -282,20 +291,26 @@
# end
def route(methods, paths = ['/'], route_options = {}, &block)
endpoint_options = {
:method => methods,
:path => paths,
- :route_options => (route_options || {}).merge(@last_description || {})
+ :route_options => (@namespace_description || {}).deep_merge(@last_description || {}).deep_merge(route_options || {})
}
endpoints << Grape::Endpoint.new(settings.clone, endpoint_options, &block)
+
@last_description = nil
+ reset_validations!
end
def before(&block)
imbue(:befores, [block])
end
+ def after_validation(&block)
+ imbue(:after_validations, [block])
+ end
+
def after(&block)
imbue(:afters, [block])
end
def get(paths = ['/'], options = {}, &block); route('GET', paths, options, &block) end
@@ -306,13 +321,17 @@
def options(paths = ['/'], options = {}, &block); route('OPTIONS', paths, options, &block) end
def patch(paths = ['/'], options = {}, &block); route('PATCH', paths, options, &block) end
def namespace(space = nil, &block)
if space || block_given?
+ previous_namespace_description = @namespace_description
+ @namespace_description = (@namespace_description || {}).deep_merge(@last_description || {})
+ @last_description = nil
nest(block) do
set(:namespace, space.to_s) if space
end
+ @namespace_description = previous_namespace_description
else
Rack::Mount::Utils.normalize_path(settings.stack.map{|s| s[:namespace]}.join('/'))
end
end
@@ -374,10 +393,11 @@
if blocks.any?
settings.push # create a new context to eval the follow
instance_eval &block if block_given?
blocks.each{|b| instance_eval &b}
settings.pop # when finished, we pop the context
+ reset_validations!
else
instance_eval &block
end
end
@@ -395,15 +415,52 @@
def initialize
@route_set = Rack::Mount::RouteSet.new
self.class.endpoints.each do |endpoint|
endpoint.mount_in(@route_set)
end
+ add_head_not_allowed_methods
@route_set.freeze
end
def call(env)
@route_set.call(env)
end
reset!
+
+ private
+
+ # 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|
+ endpoint.options[:app] && endpoint.options[:app].respond_to?(:endpoints) ?
+ endpoint.options[:app].endpoints.map(&:routes) :
+ endpoint.routes
+ end
+ resources.flatten.each do |route|
+ allowed_methods[route.route_compiled] << route.route_method
+ end
+
+ allowed_methods.each do |path_info, methods|
+ allow_header = (["OPTIONS"] | methods).join(", ")
+ unless methods.include?("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.each do |bad_method|
+ @route_set.add_route( proc { [405, { 'Allow' => allow_header }, []]}, {
+ :path_info => path_info,
+ :request_method => bad_method
+ })
+ end
+ end
+ end
+
end
end