diff options
-rw-r--r-- | config.ru | 16 | ||||
-rw-r--r-- | lib/zero.rb | 6 | ||||
-rw-r--r-- | lib/zero/rack_request.rb | 44 | ||||
-rw-r--r-- | lib/zero/router.rb | 31 | ||||
-rw-r--r-- | spec/integration/router_spec.rb | 49 | ||||
-rw-r--r-- | spec/spec_helper.rb | 14 |
6 files changed, 158 insertions, 2 deletions
diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..c2c7c62 --- /dev/null +++ b/config.ru @@ -0,0 +1,16 @@ +require File.expand_path('../lib/zero.rb', __FILE__) +require 'json' + +class Foo + def call(env) + req = Rack::Request.new(env) + [200, {'Content-Type' => 'text/html'}, ["this works #{req.params.inspect}"]] + end +end + +routes = Zero::Router.new( + '/foo/:id' => Foo.new, + '/' => Foo.new +) + +run routes diff --git a/lib/zero.rb b/lib/zero.rb index 7dca8e1..e9824f9 100644 --- a/lib/zero.rb +++ b/lib/zero.rb @@ -1,5 +1,7 @@ +require 'rack' + module Zero require_relative 'zero/controller' - require_relative 'zero/request' - require_relative 'zero/response' + require_relative 'zero/rack_request' + require_relative 'zero/router' end diff --git a/lib/zero/rack_request.rb b/lib/zero/rack_request.rb new file mode 100644 index 0000000..0a93fc5 --- /dev/null +++ b/lib/zero/rack_request.rb @@ -0,0 +1,44 @@ +# as we need #update_param we patch it it into the request +# this is directly from thr request.rb of the rack repository, so this +# can be deleted, when rack was released +r = Rack::Request.new(Rack::MockRequest.env_for('foo')) +if not r.respond_to?('update_param') then + class Rack::Request + # Destructively update a parameter, whether it's in GET and/or POST. + # Returns nil. + # + # The parameter is updated wherever it was previous defined, so + # GET, POST, or both. If it wasn't previously defined, it's inserted into GET. + # + # env['rack.input'] is not touched. + def update_param(k, v) + found = false + if self.GET.has_key?(k) + found = true + self.GET[k] = v + end + if self.POST.has_key?(k) + found = true + self.POST[k] = v + end + unless found + self.GET[k] = v + end + @params = nil + nil + end + + # Destructively delete a parameter, whether it's in GET or POST. + # Returns the value of the deleted parameter. + # + # If the parameter is in both GET and POST, the POST value takes + # precedence since that's how #params works. + # + # env['rack.input'] is not touched. + def delete_param(k) + v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first + @params = nil + v + end + end +end diff --git a/lib/zero/router.rb b/lib/zero/router.rb new file mode 100644 index 0000000..c890cb7 --- /dev/null +++ b/lib/zero/router.rb @@ -0,0 +1,31 @@ +module Zero + class Router + # match for variables in routes + VARIABLE_MATCH = %r{:(\w+)[^/]?} + # the replacement string to make it an regex + VARIABLE_REGEX = '(?<\1>.+?)' + + def initialize(routes) + @routes = {} + routes.each do |route, target| + @routes[ + Regexp.new( + route.gsub(VARIABLE_MATCH, VARIABLE_REGEX) + '$')] = target + end + end + + def call(env) + request = Rack::Request.new(env) + @routes.each do |route, target| + match = route.match(request.path) + if match + match.names.each_index do |i| + request.update_param(match.names[i], match.captures[i]) + end + return target.call(request.env) + end + end + [404, {'Content-Type' => 'text/html'}, ['Not found!']] + end + end +end diff --git a/spec/integration/router_spec.rb b/spec/integration/router_spec.rb new file mode 100644 index 0000000..3be7b3a --- /dev/null +++ b/spec/integration/router_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Zero::Router do + let(:router) { Zero::Router.new(routes) } + subject { router.call(env) } +# let(:app) do +# lambda {|env| [200, {'Content-Type' => 'text/html'}, 'correct'] } +# end + let(:app) { double } + let(:wrong_app) do + lambda {|env| [200, {'Content-Type' => 'text/html'}, 'Wrong'] } + end + + before :each do + app.should_receive(:call) + end + + context 'a working route' do + let(:routes) { { '/app' => app } } + let(:env) { generate_env('/app') } + it('takes a route') { subject } + end + + context 'select the right route' do + let(:routes) do + { '/wrong' => wrong_app, + '/correct' => app } + end + let(:env) { generate_env('/correct') } + it("selects the correct from multiple routes") { subject } + end + + context 'uses the deepest path' do + let(:routes) { { '/wrong' => wrong_app, + '/deep' => wrong_app, + '/deep/wrong' => wrong_app, + '/deep/correct' => app }} + let(:env) { generate_env('/deep/correct') } + it("finds uses the deepest path first") { subject } + end + + context 'converts parts of the url to parameters' do + let(:routes) { { '/foo/:id' => app } } + let(:env) { generate_env('/foo/42') } + it "should extract variables from the url" do + subject + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b31fe56 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,14 @@ +require 'zero' + +class SpecApp + attr_reader :env + + def call(env) + @env = env + return [200, {'Content-Type' => 'text/html'}, ['success']] + end +end + +def generate_env(path, options = {}) + Rack::MockRequest.env_for(path, options = {}) +end |