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