Permalink
Browse files

add method match to url definitions

  • Loading branch information...
1 parent b0e4c60 commit 3747bdf8acba9c828b9a09c5f2f7afa7c8660747 @caolan committed Oct 19, 2010
Showing with 172 additions and 22 deletions.
  1. +17 −3 README.md
  2. +36 −13 lib/dispatch.js
  3. +1 −1 package.json
  4. +118 −5 test/test-dispatch.js
View
20 README.md
@@ -50,14 +50,28 @@ Let's assume that 'user' is actually provided by another module:
Connect.createServer(
dispatch({
- '/about': function(req, res){ ... },
+ '/about': function(req, res){ ... },
'/user': require('./user').urls
})
);
Easy! A really lightweight and flexible URL dispatcher that just does the
-obvious. And when I say lightweight, it currently clocks in at about 26 lines
-of actual JavaScript ;)
+obvious.
+
+Its also possible to define methods for URLs:
+
+ Connect.createServer(
+ dispatch({
+ '/user': {
+ 'GET /item': function(req, res){ ... },
+ 'POST /item': function(req, res){ ... },
+ }
+ })
+ );
+
+Just prefix the URL with the http method in uppercase followed by whitespace
+and then the path you want to match against. Nested URLs always match the last
+method defined in the tree.
A couple of implementation points:
View
49 lib/dispatch.js
@@ -8,29 +8,47 @@ var url = require('url');
* {'ab': function(){}, 'ac': function(){}}
*
*/
-function flattenKeys(obj, /*optional*/acc, /*optional*/prefix){
- acc = acc || {};
+function flattenKeys(obj, /*optional args: */acc, prefix, prev_method){
+ acc = acc || [];
prefix = prefix || '';
Object.keys(obj).forEach(function(k){
- (typeof obj[k] == 'function') ?
- acc[prefix + k] = obj[k]:
- flattenKeys(obj[k], acc, prefix + k);
+ var split = splitURL(k);
+ if(typeof obj[k] == 'function') {
+ acc.push([prefix + split.url, split.method || prev_method, obj[k]])
+ }
+ else {
+ flattenKeys(obj[k], acc, prefix + split.url, split.method);
+ }
});
return acc;
}
/*
- * Compiles the keys of an object to a reqular expression, returning an array
+ * Compiles the url patterns to a reqular expression, returning an array
* of arrays.
*
- * compileKeys({'abc': function(){}, 'xyz': function(){}})
- * [[/^abc$/, function(){}], [/^xyz$/, function(){}]]
+ * compileKeys([['abc', 'GET', function(){}], ['xyz', 'POST', function(){}]])
+ * [[/^abc$/, 'GET', function(){}], [/^xyz$/, 'POST', function(){}]]
*/
function compileKeys(urls){
- return Object.keys(urls).map(function(k){
- return [new RegExp('^' + k + '$'), urls[k]];
+ return urls.map(function(url){
+ url[0] = new RegExp('^' + url[0] + '$');
+ return url;
});
-};
+}
+
+/*
+ * Break apart a url into the path matching regular expression and the
+ * optional method prefix.
+ */
+function splitURL(url) {
+ var method, path, match = /^([A-Z]+)\s+/.exec(url);
+ if (match) {
+ method = match[1];
+ url = /^[A-Z]+\s+(.*)$/.exec(url)[1];
+ }
+ return {url: url, method: method};
+}
/*
* The exported function for use as a Connect provider.
@@ -41,8 +59,13 @@ module.exports = function(urls){
return function(req, res, next){
if(!compiled.some(function(x){
var match = x[0].exec(url.parse(req.url).pathname);
- if(match) x[1].apply(null, [req, res].concat(match.slice(1)));
- return match;
+ if (match) {
+ if (!x[1] || x[1] === req.method) {
+ x[2].apply(null, [req, res].concat(match.slice(1)));
+ return true;
+ }
+ }
+ return false;
})) next();
};
};
View
2 package.json
@@ -2,7 +2,7 @@
, "description": "A regular expression URL dispatcher for Connect"
, "main": "./index"
, "author": "Caolan McMahon"
-, "version": "0.0.1"
+, "version": "0.1.0"
, "repository" :
{ "type" : "git"
, "url" : "http://github.com/caolan/dispatch.git"
View
123 test/test-dispatch.js
@@ -2,7 +2,7 @@ var dispatch = require('dispatch');
exports['simple match'] = function(test){
test.expect(2);
- var request = {url: '/test'};
+ var request = {url: '/test', method: 'GET'};
dispatch({
'/test': function(req, res){
test.equals(req, request);
@@ -13,7 +13,7 @@ exports['simple match'] = function(test){
};
exports['no match'] = function(test){
- var request = {url: '/abc'};
+ var request = {url: '/abc', method: 'XYZ'};
dispatch({
'/test': function(req, res){
test.ok(false, 'should not be called');
@@ -38,7 +38,7 @@ exports['regexp match'] = function(test){
exports['multiple matches'] = function(test){
test.expect(3);
- var request = {url: '/abc'};
+ var request = {url: '/abc', method: 'POST'};
dispatch({
'/(\\w+)/?': function(req, res, group){
test.equals(req, request);
@@ -53,7 +53,7 @@ exports['multiple matches'] = function(test){
};
exports['nested urls'] = function(test){
- var request = {url: '/folder/some/other/path'};
+ var request = {url: '/folder/some/other/path', method: 'GET'};
dispatch({
'/folder': {
'/some/other': {
@@ -68,7 +68,7 @@ exports['nested urls'] = function(test){
};
exports['nested urls with captured groups'] = function(test){
- var request = {url: '/one/two/three'};
+ var request = {url: '/one/two/three', method: 'GET'};
dispatch({
'/(\\w+)': {
'/(\\w+)': {
@@ -88,3 +88,116 @@ exports['nested urls with captured groups'] = function(test){
}
})(request, 'response', 'next');
};
+
+exports['method'] = function (test) {
+ test.expect(5);
+ var call_order = [];
+ var request = {url: '/test', method: 'GET'};
+ var handle_req = dispatch({
+ 'GET /test': function(req, res){
+ call_order.push('GET');
+ test.equals(req, request);
+ test.equals(res, 'response');
+ },
+ 'POST /test': function(req, res){
+ call_order.push('POST');
+ test.equals(req, request);
+ test.equals(res, 'response');
+ }
+ });
+ handle_req(request, 'response', 'next');
+ request.method = 'POST';
+ handle_req(request, 'response', 'next');
+ request.method = 'DELETE';
+ handle_req(request, 'response', function(){
+ test.same(call_order, ['GET', 'POST']);
+ test.done();
+ });
+};
+
+exports['nested method'] = function (test) {
+ test.expect(2);
+ var call_order = [];
+ var request = {url: '/path/test', method: 'GET'};
+ var handle_req = dispatch({
+ '/path': {
+ 'GET /test': function(req, res){
+ test.equals(req, request);
+ test.equals(res, 'response');
+ }
+ }
+ });
+ handle_req(request, 'response', function(){
+ test.ok(false, 'should not be called');
+ });
+ request.method = 'POST';
+ handle_req(request, 'response', function(){
+ test.done();
+ });
+};
+
+exports['nested already defined method'] = function (test) {
+ test.expect(2);
+ var call_order = [];
+ var request = {url: '/path/create/item', method: 'POST'};
+ var handle_req = dispatch({
+ '/path': {
+ 'POST /create': {
+ '/item': function(req, res){
+ test.equals(req, request);
+ test.equals(res, 'response');
+ }
+ }
+ }
+ });
+ handle_req(request, 'response', function(){
+ test.ok(false, 'should not be called');
+ });
+ request.method = 'GET';
+ handle_req(request, 'response', function(){
+ test.done();
+ });
+};
+
+exports['nested redefine previous method'] = function (test) {
+ test.expect(2);
+ var call_order = [];
+ var request = {url: '/path/create/item', method: 'GET'};
+ var handle_req = dispatch({
+ '/path': {
+ 'POST /create': {
+ 'GET /item': function(req, res){
+ test.equals(req, request);
+ test.equals(res, 'response');
+ }
+ }
+ }
+ });
+ handle_req(request, 'response', function(){
+ test.ok(false, 'should not be called');
+ });
+ request.method = 'POST';
+ handle_req(request, 'response', function(){
+ test.done();
+ });
+};
+
+exports['whitespace between method and pattern'] = function (test) {
+ test.expect(4);
+ var call_order = [];
+ var request = {url: '/test', method: 'GET'};
+ var handle_req = dispatch({
+ 'GET /test': function(req, res){
+ test.equals(req, request);
+ test.equals(res, 'response');
+ },
+ 'POST\t/test': function(req, res){
+ test.equals(req, request);
+ test.equals(res, 'response');
+ }
+ });
+ handle_req(request, 'response', 'next');
+ request.method = 'POST';
+ handle_req(request, 'response', 'next');
+ test.done();
+};

0 comments on commit 3747bdf

Please sign in to comment.