aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.ru16
-rw-r--r--lib/zero.rb6
-rw-r--r--lib/zero/rack_request.rb44
-rw-r--r--lib/zero/router.rb31
-rw-r--r--spec/integration/router_spec.rb49
-rw-r--r--spec/spec_helper.rb14
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