diff --git a/lib/rack/builder.rb b/lib/rack/builder.rb index db8e2d8eb..40778ce47 100644 --- a/lib/rack/builder.rb +++ b/lib/rack/builder.rb @@ -46,13 +46,13 @@ def self.parse_file(config, opts = Server::Options.new) return app, options end - def initialize(&block) - @ins = [] + def initialize(default_app = nil,&block) + @use, @map, @run = [], nil, default_app instance_eval(&block) if block_given? end - def self.app(&block) - self.new(&block).to_app + def self.app(default_app = nil, &block) + self.new(default_app, &block).to_app end # Specifies a middleware to use in a stack. @@ -75,7 +75,11 @@ def self.app(&block) # The +call+ method in this example sets an additional environment key which then can be # referenced in the application if required. def use(middleware, *args, &block) - @ins << lambda { |app| middleware.new(app, *args, &block) } + if @map + mapping, @map = @map, nil + @use << proc { |app| generate_map app, mapping } + end + @use << proc { |app| middleware.new(app, *args, &block) } end # Takes an argument that is an object that responds to #call and returns a Rack response. @@ -93,7 +97,7 @@ def use(middleware, *args, &block) # # run Heartbeat def run(app) - @ins << app #lambda { |nothing| app } + @run = app end # Creates a route within the application. @@ -116,22 +120,26 @@ def run(app) # This example includes a piece of middleware which will run before requests hit +Heartbeat+. # def map(path, &block) - if @ins.last.kind_of? Hash - @ins.last[path] = self.class.new(&block).to_app - else - @ins << {} - map(path, &block) - end + @map ||= {} + @map[path] = block end def to_app - @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last - inner_app = @ins.last - @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) } + app = @map ? generate_map(@run, @map) : @run + fail "missing run or map statement" unless app + @use.reverse.inject(app) { |a,e| e[a] } end def call(env) to_app.call(env) end + + private + + def generate_map(default_app, mapping) + mapped = default_app ? {'/' => default_app} : {} + mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) } + URLMap.new(mapped) + end end end diff --git a/test/spec_builder.rb b/test/spec_builder.rb index f7501ae42..d3e7261da 100644 --- a/test/spec_builder.rb +++ b/test/spec_builder.rb @@ -99,6 +99,26 @@ def self.env Rack::MockRequest.new(app).get("/").should.be.server_error end + it "can mix map and run for endpoints" do + app = Rack::Builder.app do + map('/sub') { run lambda { |inner_env| [200, {}, ['sub']] }} + run lambda { |inner_env| [200, {}, ['root']] } + end + + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root' + Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub' + end + + it "accepts middleware-only map blocks" do + app = Rack::Builder.app do + map('/foo') { use Rack::ShowExceptions } + run lambda { |env| raise "bzzzt" } + end + + proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError) + Rack::MockRequest.new(app).get("/foo").should.be.server_error + end + should "initialize apps once" do app = Rack::Builder.new do class AppClass @@ -120,6 +140,23 @@ def call(env) Rack::MockRequest.new(app).get("/").should.be.server_error end + it "allows use after run" do + app = Rack::Builder.app do + run lambda { |env| raise "bzzzt" } + use Rack::ShowExceptions + end + + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + end + + it 'complains about a missing run' do + proc do + Rack::Builder.app { use Rack::ShowExceptions } + end.should.raise(RuntimeError) + end + describe "parse_file" do def config_file(name) File.join(File.dirname(__FILE__), 'builder', name)