lib/rack/api/runner.rb in rack-api-0.1.0 vs lib/rack/api/runner.rb in rack-api-0.1.1

- old
+ new

@@ -5,28 +5,82 @@ attr_accessor :settings def initialize @settings = { - :prefix => "/", - :formats => %w[json jsonp], - :middlewares => [] + :middlewares => [], + :helpers => [], + :global => { + :prefix => "/", + :formats => %w[json jsonp], + :middlewares => [], + :helpers => [] + } } end + # Set configuration based on scope. When defining values outside version block, + # will set configuration using <tt>settings[:global]</tt> namespace. + # + # Use the Rack::API::Runner#option method to access a given setting. + # + def set(name, value, mode = :override) + target = settings[:version] ? settings : settings[:global] + + if mode == :override + target[name] = value + else + target[name] << value + end + end + + # Try to fetch local configuration, defaulting to the global setting. + # Return +nil+ when no configuration is defined. + # + def option(name, mode = :any) + if mode == :merge && (settings[name].kind_of?(Array) || settings[:global][name].kind_of?(Array)) + settings[:global].fetch(name, []) | settings.fetch(name, []) + else + settings.fetch(name, settings[:global][name]) + end + end + # Add a middleware to the execution stack. # + # Global middlewares will be merged with local middlewares. + # + # Rack::API.app do + # use ResponseTime + # + # version :v1 do + # use Gzip + # end + # end + # + # The middleware stack will be something like <tt>[ResponseTime, Gzip]</tt>. + # def use(middleware, *args) - settings[:middlewares] << [middleware, *args] + set :middlewares, [middleware, *args], :append end # Set an additional url prefix. # def prefix(name) - settings[:prefix] = name + set :prefix, name end + # Add a helper to application. + # + # helper MyHelpers + # helper { } + # + def helper(mod = nil, &block) + mod = Module.new(&block) if block_given? + raise ArgumentError, "you need to pass a module or block" unless mod + set :helpers, mod, :append + end + # Create a new API version. # def version(name, &block) raise ArgumentError, "you need to pass a block" unless block_given? settings[:version] = name.to_s @@ -46,12 +100,38 @@ # Rack::API.app do # basic_auth "Protected Area" do |user, pass| # User.authenticate(user, pass) # end # end + # + # You can disable basic authentication by providing <tt>:none</tt> as + # realm. + # + # Rack::API.app do + # basic_auth "Protected Area" do |user, pass| + # User.authenticate(user, pass) + # end + # + # version :v1 do + # # this version is protected by the + # # global basic auth block above. + # end + # + # version :v2 do + # basic_auth :none + # # this version is now public + # end + # + # version :v3 do + # basic_auth "Admin" do |user, pass| + # user == "admin" && pass == "test" + # end + # end + # end + # def basic_auth(realm = "Restricted Area", &block) - settings[:auth] = [realm, block] + set :auth, (realm == :none ? :none : [realm, block]) end # Define the formats that this app implements. # Respond only to <tt>:json</tt> by default. # @@ -63,12 +143,24 @@ # It will raise an exception if no formatter method has been defined. # # See Rack::API::Formatter::Jsonp for an example on how to create additional # formatters. # + # Local formats will override the global configuration on that context. + # + # Rack::API.app do + # respond_to :json, :xml, :jsonp + # + # version :v1 do + # respond_to :json + # end + # end + # + # The code above will accept only <tt>:json</tt> as format on version <tt>:v1</tt>. + # def respond_to(*formats) - settings[:formats] = formats + set :formats, formats end # Hold all routes. # def route_set # :nodoc: @@ -100,18 +192,33 @@ route("#{method.upcase}", *args, &block) # route("GET", *args, &block) end # end RUBY end + private def mount_path(path) # :nodoc: - Rack::Mount::Utils.normalize_path([settings[:prefix], settings[:version], path].join("/")) + Rack::Mount::Utils.normalize_path([option(:prefix), settings[:version], path].join("/")) end def build_app(block) # :nodoc: + app = App.new(:block => block) builder = Rack::Builder.new - builder.use Rack::Auth::Basic, settings[:auth][0], &settings[:auth][1] if settings[:auth] - settings[:middlewares].each {|middleware| builder.use(*middleware)} - builder.run App.new(:block => block) + + # Add middleware for basic authentication. + auth = option(:auth) + builder.use Rack::Auth::Basic, auth[0], &auth[1] if auth && auth != :none + + # Add middleware for format validation. + builder.use Rack::API::Middleware::Format, option(:formats) + + # Add middlewares to executation stack. + option(:middlewares, :merged).each {|middleware| builder.use(*middleware)} + + # Apply helpers to app. + helpers = option(:helpers) + app.extend *helpers unless helpers.empty? + + builder.run(app) builder.to_app end end end end