Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

init

  • Loading branch information...
commit 9ea9db17b98a9e8f84366ca455e41718549faf8c 0 parents
@kilianc authored
3  .travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.6
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Kilian Ciuffolo, me@nailik.org
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
18 Makefile
@@ -0,0 +1,18 @@
+REPORTER = spec
+
+test:
+ @NODE_ENV=test ./node_modules/.bin/mocha test/*-test.js $(OPT) --reporter $(REPORTER)
+
+test-bail:
+ $(MAKE) test OPT=--bail
+
+test-cov:
+ $(MAKE) lib-cov
+ @APISERVER_ROUTER_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
+ @open -g coverage.html
+
+lib-cov:
+ @rm -rf lib-cov
+ @jscoverage lib lib-cov
+
+.PHONY: test test-ba test-cov lib-cov
138 README.md
@@ -0,0 +1,138 @@
+# apiserver-router [![build status](https://secure.travis-ci.org/kilianc/node-apiserver-router.png?branch=master)](http://travis-ci.org/kilianc/node-apiserver-router)
+
+A fast API router with integrated caching system bundled with [ApiServer](https://github.com/kilianc/node-apiserver)
+
+## Installation
+
+ ⚡ npm install apiserver-router
+
+```javascript
+var Router = require('apiserver-router')
+```
+
+## Example
+
+```javascript
+var UserModule = function (options) {
+ this.options = options
+}
+
+// will be translated into /1/random_photo_module/create_album
+UserModule.prototype.createAlbum = function (request, response) { ... }
+
+// will be translated into /1/random_photo_module/upload_photo
+UserModule.prototype.uploadPhoto = function (request, response) { ... }
+
+// private method, skipped
+UserModule.prototype._checkFileExtension = function (request, response) { ... }
+
+```
+
+```javascript
+router.update({ '1': { randomPhotoModule: UserModule } })
+router.get('/1/random_photo_module/upload_photo') // returns the associated chain
+```
+
+## Class Method: constructor
+
+### Syntax:
+
+```javascript
+new Router()
+```
+
+## Class Method: update
+
+Builds and caches the routes. You must call it every time a middleware or a module changes.
+
+### Syntax:
+
+```javascript
+Router.prototype.update(modules[, middlewareList])
+```
+
+### Arguments:
+
+* __modules__ - (`Object`) an hashtable of [API modules](https://github.com/kilianc/node-apiserver/tree/master#modules)
+* __middlewareList__ - (`Array`) a list of [middleware](https://github.com/kilianc/node-apiserver/tree/master#middleware)
+
+### Example:
+
+```javascript
+...
+var router = new Router()
+router.update({
+ v1: {
+ user: {
+ signin: function (request, response) { /* function body */ },
+ signout: function (request, response) { /* function body */ },
+ signup: function (request, response) { /* function body */ }
+ }
+ }
+}, [
+ { route: /signup/, handle: randomMiddleware1 },
+ { route: /sign/, handle: randomMiddleware2 }
+])
+...
+```
+
+## Class Method: get
+
+This method returns a list of functions that will be executed with [fnchain](https://github.com/kilianc/node-fnchain). The list will contain all the middleware active for the API endpoint reached by `pathname` and as last ring of the chain the API method to execute.
+
+### Syntax:
+
+```javascript
+Router.prototype.get(pathname)
+```
+
+### Arguments:
+
+* __pathname__ - (`String`)
+
+### Example:
+
+```javascript
+...
+router.get('/v1/users/signup')
+...
+```
+
+# How to contribute
+
+This repository follows (more or less) the [Felix's Node.js Style Guide](http://nodeguide.com/style.html), your contribution must be consistent with this style.
+
+The test suite is written on top of [visionmedia/mocha](http://visionmedia.github.com/mocha/) and it took hours of hard work. Please use the tests to check if your contribution breaks some part of the library and add new tests for each new feature.
+
+ ⚡ npm test
+
+and for your test coverage
+
+ ⚡ make test-cov
+
+## License
+
+_This software is released under the MIT license cited below_.
+
+ Copyright (c) 2010 Kilian Ciuffolo, me@nailik.org. All Rights Reserved.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the 'Software'), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
3  index.js
@@ -0,0 +1,3 @@
+module.exports = process.env.APISERVER_ROUTER_COV
+ ? require('./lib-cov/router')
+ : require('./lib/router')
131 lib-cov/router.js
@@ -0,0 +1,131 @@
+/* automatically generated by JSCoverage - do not edit */
+if (typeof _$jscoverage === 'undefined') _$jscoverage = {};
+if (! _$jscoverage['router.js']) {
+ _$jscoverage['router.js'] = [];
+ _$jscoverage['router.js'][1] = 0;
+ _$jscoverage['router.js'][2] = 0;
+ _$jscoverage['router.js'][5] = 0;
+ _$jscoverage['router.js'][6] = 0;
+ _$jscoverage['router.js'][7] = 0;
+ _$jscoverage['router.js'][8] = 0;
+ _$jscoverage['router.js'][9] = 0;
+ _$jscoverage['router.js'][10] = 0;
+ _$jscoverage['router.js'][11] = 0;
+ _$jscoverage['router.js'][12] = 0;
+ _$jscoverage['router.js'][13] = 0;
+ _$jscoverage['router.js'][15] = 0;
+ _$jscoverage['router.js'][16] = 0;
+ _$jscoverage['router.js'][17] = 0;
+ _$jscoverage['router.js'][20] = 0;
+ _$jscoverage['router.js'][21] = 0;
+ _$jscoverage['router.js'][22] = 0;
+ _$jscoverage['router.js'][23] = 0;
+ _$jscoverage['router.js'][24] = 0;
+ _$jscoverage['router.js'][25] = 0;
+ _$jscoverage['router.js'][27] = 0;
+ _$jscoverage['router.js'][33] = 0;
+ _$jscoverage['router.js'][36] = 0;
+ _$jscoverage['router.js'][37] = 0;
+ _$jscoverage['router.js'][40] = 0;
+ _$jscoverage['router.js'][41] = 0;
+ _$jscoverage['router.js'][42] = 0;
+ _$jscoverage['router.js'][43] = 0;
+ _$jscoverage['router.js'][46] = 0;
+ _$jscoverage['router.js'][47] = 0;
+ _$jscoverage['router.js'][48] = 0;
+ _$jscoverage['router.js'][49] = 0;
+ _$jscoverage['router.js'][50] = 0;
+ _$jscoverage['router.js'][51] = 0;
+ _$jscoverage['router.js'][55] = 0;
+}
+_$jscoverage['router.js'][1]++;
+var Router = module.exports = (function () {
+ _$jscoverage['router.js'][2]++;
+ this.routes = {};
+});
+_$jscoverage['router.js'][5]++;
+Router.prototype.update = (function (modules, middlewareList) {
+ _$jscoverage['router.js'][6]++;
+ var routes = Object.create(null);
+ _$jscoverage['router.js'][7]++;
+ var map = Object.create(null);
+ _$jscoverage['router.js'][8]++;
+ Object.keys(modules).forEach((function (version) {
+ _$jscoverage['router.js'][9]++;
+ Object.keys(modules[version]).forEach((function (moduleName) {
+ _$jscoverage['router.js'][10]++;
+ var module_name = moduleName.replace(/([A-Z][a-z][^A-Z]*)/g, "_$1").toLowerCase();
+ _$jscoverage['router.js'][11]++;
+ Object.keys(modules[version][moduleName]).forEach((function (methodName) {
+ _$jscoverage['router.js'][12]++;
+ if (methodName[0] === "_") {
+ _$jscoverage['router.js'][13]++;
+ return;
+ }
+ _$jscoverage['router.js'][15]++;
+ var route = getRoutePath(version, module_name, methodName);
+ _$jscoverage['router.js'][16]++;
+ if (routes[route]) {
+ _$jscoverage['router.js'][17]++;
+ throw new Error("Routing conflict on \"" + route + "\": " + map[route].version + "." + map[route].moduleName + "." + map[route].methodName + " is anbiguous with " + version + "." + moduleName + "." + methodName);
+ }
+ _$jscoverage['router.js'][20]++;
+ map[route] = {version: version, moduleName: moduleName, methodName: methodName};
+ _$jscoverage['router.js'][21]++;
+ var chain = routes[route] = getChain(middlewareList, route);
+ _$jscoverage['router.js'][22]++;
+ chain.push((function (request, response, callback) {
+ _$jscoverage['router.js'][23]++;
+ try {
+ _$jscoverage['router.js'][24]++;
+ modules[version][moduleName][methodName](request, response);
+ _$jscoverage['router.js'][25]++;
+ callback();
+ }
+ catch (err) {
+ _$jscoverage['router.js'][27]++;
+ callback(err);
+ }
+}));
+}));
+}));
+}));
+ _$jscoverage['router.js'][33]++;
+ this.routes = routes;
+});
+_$jscoverage['router.js'][36]++;
+Router.prototype.get = (function (pathname) {
+ _$jscoverage['router.js'][37]++;
+ return this.routes[pathname];
+});
+_$jscoverage['router.js'][40]++;
+function getRoutePath(version, module_name, methodName) {
+ _$jscoverage['router.js'][41]++;
+ var words = [];
+ _$jscoverage['router.js'][42]++;
+ methodName.replace(/([A-Za-z][^A-Z]*)/g, (function (match) {
+ _$jscoverage['router.js'][42]++;
+ words.push(match);
+}));
+ _$jscoverage['router.js'][43]++;
+ return ["", version, module_name, words.join("_")].join("/").toLowerCase();
+}
+_$jscoverage['router.js'][46]++;
+function getChain(middlewareList, route) {
+ _$jscoverage['router.js'][47]++;
+ var chain = [];
+ _$jscoverage['router.js'][48]++;
+ if (middlewareList) {
+ _$jscoverage['router.js'][49]++;
+ middlewareList.forEach((function (middleware) {
+ _$jscoverage['router.js'][50]++;
+ if (route.match(middleware.route)) {
+ _$jscoverage['router.js'][51]++;
+ chain.push(middleware.handle);
+ }
+}));
+ }
+ _$jscoverage['router.js'][55]++;
+ return chain;
+}
+_$jscoverage['router.js'].source = ["var Router = module.exports = function () {"," this.routes = {}","}","","Router.prototype.update = function (modules, middlewareList) {"," var routes = Object.create(null)"," var map = Object.create(null)"," Object.keys(modules).forEach(function (version) {"," Object.keys(modules[version]).forEach(function (moduleName) {"," var module_name = moduleName.replace(/([A-Z][a-z][^A-Z]*)/g, '_$1').toLowerCase()"," Object.keys(modules[version][moduleName]).forEach(function (methodName) {"," if (methodName[0] === '_') {"," return"," }"," var route = getRoutePath(version, module_name, methodName)"," if (routes[route]) {"," throw new Error('Routing conflict on \"' + route + '\": ' + map[route].version + '.' + map[route].moduleName + '.' + map[route].methodName +"," ' is anbiguous with ' + version + '.' + moduleName + '.' + methodName)"," }"," map[route] = { version: version, moduleName: moduleName, methodName: methodName }"," var chain = routes[route] = getChain(middlewareList, route)"," chain.push(function (request, response, callback) {"," try {"," modules[version][moduleName][methodName](request, response)"," callback()"," } catch (err) {"," callback(err)"," }"," })"," })"," })"," })"," this.routes = routes","}","","Router.prototype.get = function (pathname) {"," return this.routes[pathname]","}","","function getRoutePath(version, module_name, methodName) {"," var words = []"," methodName.replace(/([A-Za-z][^A-Z]*)/g, function (match) { words.push(match) })"," return ['', version, module_name, words.join('_')].join('/').toLowerCase()","}","","function getChain(middlewareList, route) {"," var chain = []"," if (middlewareList) {"," middlewareList.forEach(function (middleware) {"," if (route.match(middleware.route)) {"," chain.push(middleware.handle)"," }"," })"," }"," return chain","}"];
56 lib/router.js
@@ -0,0 +1,56 @@
+var Router = module.exports = function () {
+ this.routes = {}
+}
+
+Router.prototype.update = function (modules, middlewareList) {
+ var routes = Object.create(null)
+ var map = Object.create(null)
+ Object.keys(modules).forEach(function (version) {
+ Object.keys(modules[version]).forEach(function (moduleName) {
+ var module_name = moduleName.replace(/([A-Z][a-z][^A-Z]*)/g, '_$1').toLowerCase()
+ Object.keys(modules[version][moduleName]).forEach(function (methodName) {
+ if (methodName[0] === '_') {
+ return
+ }
+ var route = getRoutePath(version, module_name, methodName)
+ if (routes[route]) {
+ throw new Error('Routing conflict on "' + route + '": ' + map[route].version + '.' + map[route].moduleName + '.' + map[route].methodName +
+ ' is anbiguous with ' + version + '.' + moduleName + '.' + methodName)
+ }
+ map[route] = { version: version, moduleName: moduleName, methodName: methodName }
+ var chain = routes[route] = getChain(middlewareList, route)
+ chain.push(function (request, response, callback) {
+ try {
+ modules[version][moduleName][methodName](request, response)
+ callback()
+ } catch (err) {
+ callback(err)
+ }
+ })
+ })
+ })
+ })
+ this.routes = routes
+}
+
+Router.prototype.get = function (pathname) {
+ return this.routes[pathname]
+}
+
+function getRoutePath(version, module_name, methodName) {
+ var words = []
+ methodName.replace(/([A-Za-z][^A-Z]*)/g, function (match) { words.push(match) })
+ return ['', version, module_name, words.join('_')].join('/').toLowerCase()
+}
+
+function getChain(middlewareList, route) {
+ var chain = []
+ if (middlewareList) {
+ middlewareList.forEach(function (middleware) {
+ if (route.match(middleware.route)) {
+ chain.push(middleware.handle)
+ }
+ })
+ }
+ return chain
+}
24 package.json
@@ -0,0 +1,24 @@
+{
+ "author": "Kilian Ciuffolo <me@nailik.org> (http://nailik.org)",
+ "name": "apiserver-router",
+ "description": "A fast API router with integrated caching system",
+ "keywords": ["apiserver", "cache", "router"],
+ "version": "0.1.0",
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/kilianc/node-apiserver-router.git"
+ },
+ "main" : "./",
+ "scripts": {
+ "test": "make test"
+ },
+ "engines": {
+ "node": ">= v0.6.x"
+ },
+ "dependencies": {
+ },
+ "devDependencies": {
+ "mocha": "1.0.x",
+ "should": "0.6.1"
+ }
+}
17 test/fixtures/router-module.js
@@ -0,0 +1,17 @@
+module.exports = {
+ amAPublicApi: function (request, response) {
+ response.serveJSON({ success: true })
+ },
+ _notReachableAPI: function (request, response) {
+ response.serveJSON({ success: true })
+ },
+ errorApi: function (request, response) {
+ throw new Error('Aww')
+ },
+ 'post': function (request, response) {
+ response.serveJSON(request.body)
+ },
+ 'wrong_case_api': function (request, response) {
+ response.serveJSON({ success: true })
+ }
+}
1  test/mocha.opts
@@ -0,0 +1 @@
+--slow 1000
80 test/router-test.js
@@ -0,0 +1,80 @@
+var should = require('should'),
+ Router = require('../'),
+ testModule = require('./fixtures/router-module')
+
+var router
+var fakeMiddlewares = []
+
+for (var i = 0; i < 6; i++) {
+ fakeMiddlewares.push(fakeMiddleware(i))
+}
+
+function fakeMiddleware(i) {
+ return { index: i, handle: function () {} }
+}
+
+function checkLastChainLink(fn) {
+ fn(null, null, function (err) {
+ should.exist(err)
+ })
+ fn({ body: '' }, { serveJSON: function () {} }, function (err) {
+ if (err && err.message !== 'Aww') {
+ throw new Error('err shouldn\'t exist or have a different message than "Aww" ' + (err && err.message))
+ }
+ })
+}
+
+function checkChain(router, route, expetedChain) {
+ var chain = router.get(route)
+ checkLastChainLink(chain.pop())
+ chain.should.eql(expetedChain, route)
+}
+
+describe('Router', function () {
+ before(function () {
+ router = new Router()
+ router.update({ v1: { test: testModule } }, [
+ { route: /.+/, handle: fakeMiddlewares[0] },
+ { route: /\/am_a/, handle: fakeMiddlewares[1] },
+ { route: /api/, handle: fakeMiddlewares[2] },
+ { route: /error/, handle: fakeMiddlewares[3] },
+ { route: /\/v1\/test\/post/, handle: fakeMiddlewares[4] },
+ { route: /\/v1\/test\/.+o.+/, handle: fakeMiddlewares[5] }
+ ])
+ })
+ it('should skip private methods', function () {
+ new should.Assertion(router.routes).not.have.property('/v1/test/_not_reachable')
+ })
+ it('should create routes', function () {
+ new should.Assertion(router.routes).have.keys([
+ '/v1/test/am_a_public_api',
+ '/v1/test/error_api',
+ '/v1/test/post',
+ '/v1/test/wrong_case_api'
+ ])
+ })
+ it('should routes must be Array', function () {
+ router.routes['/v1/test/am_a_public_api'].should.be.an.instanceof(Array)
+ router.routes['/v1/test/error_api'].should.be.an.instanceof(Array)
+ router.routes['/v1/test/post'].should.be.an.instanceof(Array)
+ router.routes['/v1/test/wrong_case_api'].should.be.an.instanceof(Array)
+ })
+ it('should map correctly routes to middleware', function () {
+ checkChain(router, '/v1/test/am_a_public_api', [fakeMiddlewares[0], fakeMiddlewares[1], fakeMiddlewares[2]])
+ checkChain(router, '/v1/test/error_api', [fakeMiddlewares[0], fakeMiddlewares[2], fakeMiddlewares[3], fakeMiddlewares[5]])
+ checkChain(router, '/v1/test/post', [fakeMiddlewares[0], fakeMiddlewares[4], fakeMiddlewares[5]])
+ checkChain(router, '/v1/test/wrong_case_api', [fakeMiddlewares[0], fakeMiddlewares[2], fakeMiddlewares[5]])
+ })
+ ;[
+ ['apiName', 'api_name'],
+ ['apiLongName', 'api_long_name'],
+ ['AA', 'a_a']
+ ].forEach(function (names) {
+ var conflictsModule = { v1: { test: {} } }
+ conflictsModule.v1.test[names[0]] = new Function()
+ conflictsModule.v1.test[names[1]] = new Function()
+ it('should throw error on ambiguous modules [' + names[0] + ', ' + names[1] + ']', function () {
+ router.update.bind(router, conflictsModule).should.throw(/^Routing conflict /)
+ })
+ })
+})
Please sign in to comment.
Something went wrong with that request. Please try again.