Skip to content

Commit

Permalink
Merge pull request #25 from budnieswski/master
Browse files Browse the repository at this point in the history
Group method + Travis test
  • Loading branch information
ukayani authored Jul 10, 2020
2 parents 26d0cfb + 1ed2f33 commit 76acccb
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 16 deletions.
6 changes: 0 additions & 6 deletions .travis.yml

This file was deleted.

65 changes: 58 additions & 7 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,54 @@ function getHandlersFromArgs (args, start) {
return process(args, []);
}

const RESTIFY_METHODS = ['get', 'post', 'put', 'del', 'patch', 'head', 'opts'];

const RESTIFY_METHODS = ['get', 'post', 'put', 'del', 'patch', 'head', 'opts', 'group'];

/**
* Simple router that aggregates restify routes
*/
function Router () {

// Routes with all verbs
const routes = {};
RESTIFY_METHODS.forEach(function (method) {
if (method === 'group') {
return;
}
routes[method] = [];
});

this.routes = routes;
this.commonHandlers = [];
this.routers = [];
this.groupMiddlewares = [];
this.groupPrefix = '';
}

/**
* Merge path along side with group nest
* @param {Router} router - a Router instance
* @param {Object} opts - Route options
* @returns {Object}
*/
function mergeWithGroupPrefix (router, opts) {
if (!router.groupPrefix || !opts.path) {
return opts;
}

if (typeof opts !== 'object') {
throw new TypeError('opts (Route options) required');
}

if (!(router instanceof Router)) {
throw new TypeError('router (Router) required');
}

const path = concat(router.groupPrefix, opts.path);

// Avoid extra slash
opts.path = path.length > 1 && path.substr(-1) === '/' ? path.substr(0, path.length - 1) : path;

return opts;
}

/**
Expand All @@ -71,14 +103,33 @@ RESTIFY_METHODS.forEach(function (method) {
throw new TypeError('handler (function) required');
}

const route = {
options: opts,
handlers: getHandlersFromArgs(arguments, 1)
};
if (method === 'group') {
if (typeof opts.path === 'undefined') {
throw new TypeError('group (path) required');
}

this.groupPrefix = concat(this.groupPrefix, opts.path);

const handlers = getHandlersFromArgs(arguments, 1);
const groupHandler = handlers.pop();

this.groupMiddlewares = this.groupMiddlewares.concat(handlers);

this.routes[method].push(route);
groupHandler(this);

this.groupPrefix = '';
this.groupMiddlewares = [];
} else {
const route = {
options: mergeWithGroupPrefix(this, opts),
handlers: this.groupMiddlewares.concat(getHandlersFromArgs(arguments, 1))
};

this.routes[method].push(route);
}
};


});

/**
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "restify-router",
"version": "0.5.1",
"version": "0.6.0",
"description": "A router interface for restify that lets you aggregate route definitions and apply to a restify server",
"main": "index.js",
"engines": {
Expand Down
209 changes: 209 additions & 0 deletions test/router.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,215 @@ describe('Restify Router', function () {
});
});

describe('Nested routers via .group', function () {
it('Should allow arbitrary nesting of routers', function (done) {
var router = new Router();

router.group('/v1', function (router) {
router.group('/auth', function (router) {
router.post('/register', function (req, res, next) {
res.send({
status: 'success',
name: req.body.name
});
return next();
});
});
});

router.applyRoutes(server);

request(server)
.post('/v1/auth/register')
.set('Content-Type', 'application/json')
.send({name: 'test'})
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}

res.body.should.deep.equal({
status: 'success',
name: 'test'
});
done();
});

});

it('Should allow nesting routers using regex paths', function (done) {
var router = new Router();

router.group(/^\/foo+/, function (router) {
router.group(/^\/ba+r/, function (router) {
router.get('/bam', function (req, res, next) {
res.send(200);
return next();
});
});
});

router.applyRoutes(server);

request(server)
.get('/fooo/baar/bam')
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
done();
});

});

it('Should fail if invalid path type is provided for router', function () {

function fail() {
var router = new Router();

router.group({}, function (router) {
router.get('/bam', function (req, res, next) {
res.send(200);
return next();
});
});
}

/* jshint ignore:start */
expect(fail).to.throw('group (path) required');
/* jshint ignore:end */
});
});

describe('Nested routers via .group should process middlewares in order', function () {
it('Should process middlewares in order', function (done) {
var router = new Router();

var first = function (req, res, next) {
if (req.test && req.test.constructor === Array) {
req.test.push(1);
} else {
req.test = [1];
}
next();
};

var second = function (req, res, next) {
req.test.push(2);
next();
};

var third = function (req, res, next) {
req.test.push(3);
next();
};

router.group('/v1', first, function (router) {
router.group('/auth', second, third, function (router) {
router.post('/register', function (req, res, next) {
res.send({
status: 'success',
name: req.body.name,
commonHandlerInjectedValues: req.test
});
return next();
});
});
});

router.applyRoutes(server);

request(server)
.post('/v1/auth/register')
.set('Content-Type', 'application/json')
.send({name: 'test'})
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}

res.body.should.deep.equal({
status: 'success',
name: 'test',
commonHandlerInjectedValues: [1, 2, 3]
});
done();
});

});

it('Should process middlewares with common middleware in order', function (done) {
var router = new Router();

var common = function (req, res, next) {
if (req.test && req.test.constructor === Array) {
req.test.push(0);
} else {
req.test = [0];
}
next();
};

var first = function (req, res, next) {
if (req.test && req.test.constructor === Array) {
req.test.push(1);
} else {
req.test = [1];
}
next();
};

var second = function (req, res, next) {
req.test.push(2);
next();
};

var third = function (req, res, next) {
req.test.push(3);
next();
};

router.use(common);

router.group('/v1', first, function (router) {
router.group('/auth', second, third, function (router) {
router.post('/register', function (req, res, next) {
res.send({
status: 'success',
name: req.body.name,
commonHandlerInjectedValues: req.test
});
return next();
});
});
});

router.applyRoutes(server);

request(server)
.post('/v1/auth/register')
.set('Content-Type', 'application/json')
.send({name: 'test'})
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}

res.body.should.deep.equal({
status: 'success',
name: 'test',
commonHandlerInjectedValues: [0, 1, 2, 3]
});
done();
});

});
});

describe('Common failure cases', function () {

it('Should fail if invalid path type is provided', function () {
Expand Down
17 changes: 17 additions & 0 deletions types/restify-router/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,27 @@ declare module 'restify-router' {
opts: Array<Route>;
}

export type GroupHandler = (router: Router) => void;

export type GroupHandlerType = GroupHandler | RequestHandler | RequestHandler[];

export class Router {

routes: RouteCollection;

groupPrefix?: string;

groupMiddlewares?: RequestHandler[];

/**
* Mounts a chain on the given path against this HTTP verb
*
* @param opts if string, the URL to handle.
* if options, the URL to handle, at minimum.
* @returns the newly created route.
*/
group(path: string | RegExp | RouteOptions, ...handlers: GroupHandlerType[]): void;

/**
* Mounts a chain on the given path against this HTTP verb
*
Expand Down
23 changes: 23 additions & 0 deletions types/restify-router/restify-router-group-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// tslint:disable file-name-casing

import { Server, createServer, Request, Response, Next } from 'restify'
import { Router } from 'restify-router'

const router = new Router()
const restifyServer1: Server = createServer({})

router.get('/', (req: Request, res: Response, next: Next) => { res.send(200); });

router.group('/a', function (router: Router) {
router.get('/foo-get', (req: Request, res: Response, next: Next) => { res.send(200); });
router.post('/foo-post', (req: Request, res: Response, next: Next) => { res.send(200); });
router.put('/foo-put', (req: Request, res: Response, next: Next) => { res.send(200); });
router.del('/foo-del', (req: Request, res: Response, next: Next) => { res.send(200); });
router.patch('/foo-patch', (req: Request, res: Response, next: Next) => { res.send(200); });
router.head('/foo-head', (req: Request, res: Response, next: Next) => { res.send(200); });
router.opts('/foo-opts', (req: Request, res: Response, next: Next) => { res.send(200); });
})

router.applyRoutes(restifyServer1, 'prefix1');


3 changes: 2 additions & 1 deletion types/restify-router/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"files": [
"index.d.ts",
"restify-router-tests.ts"
"restify-router-tests.ts",
"restify-router-group-tests.ts"
]
}

0 comments on commit 76acccb

Please sign in to comment.