*/
const { METHODS: httpMethods } = require('http');
-const common = require('./common');
-const { DingusError } = require('./errors');
+const common = require('../common');
+const { DingusError } = require('../errors');
+const PathParameter = require('./path-parameter');
// Internal identifiers for route entries.
-const METHODS = Symbol('METHODS');
-const PARAM = Symbol('PARAM');
+const kPathMethods = Symbol('kSqueepDingusRouterPathMethods');
const defaultOptions = {
ignoreTrailingSlash: false,
* mapping of methods to handler functions.
*
* @property {Object} pathsByLength index to registered paths by number of parts
- * @property {Symbol} METHODS key to method:handler map on search paths
- * @property {Symbol} PARAM key to parameter name in search path parts
*/
class Router {
/**
this.pathsByLength = {
1: [],
};
-
- this.METHODS = METHODS;
- this.PARAM = PARAM;
}
+
/**
- * @typedef {Object} Router~ParamPart
- * @property {String} PARAM (symbol key)
- */
- /**
- * @typedef {Array<String|Router~ParamPart>} Router~SearchPath
- * @property {Object} METHODS (symbol key)
+ * @typedef {Array<String|PathParameter>} Router~RoutePath
+ * @property {Object} kPathMethods (symbol key)
*/
/**
* Prepare a path for insertion into search list.
- * A searchable path is a list of path parts, with a property of an object of method handlers.
- * @param {String} pathDefinition
- * @returns {Router~SearchPath}
+ * A route path is an Array of path parts, with a symbolic property of an object mapping method handlers.
+ * @param {String} rawPath
+ * @returns {Router~RoutePath}
* @private
*/
- _pathDefinitionToPathMatch(pathDefinition) {
- const pathMatch = pathDefinition
+ _pathToRoutePath(rawPath) {
+ const routePath = rawPath
.split('/')
- .map((p) => p.startsWith(this.paramPrefix) ? { [PARAM]: p.slice(this.paramPrefix.length) } : p);
+ .map((p) => p.startsWith(this.paramPrefix) ? new PathParameter(p.slice(this.paramPrefix.length)) : p);
if (this.ignoreTrailingSlash
- && pathMatch[pathMatch.length - 1] === '') {
- pathMatch.pop();
+ && routePath[routePath.length - 1] === '') {
+ routePath.pop();
}
- pathMatch[METHODS] = {};
- pathMatch.forEach((p) => Object.freeze(p));
- Object.freeze(pathMatch);
- return pathMatch;
+ routePath[kPathMethods] = {};
+ Object.freeze(routePath);
+ return routePath;
}
/**
* Compare checkPath to fixedPath, no param substitution, params must match.
- * @param {Router~SearchPath} fixedPath
- * @param {Router~SearchPath} checkPath
+ * @param {Router~RoutePath} routePath
+ * @param {Router~RoutePath} checkPath
* @returns {Boolean}
* @private
*/
- static _pathCompareExact(fixedPath, checkPath) {
- if (fixedPath.length !== checkPath.length) {
+ static _pathCompareExact(routePath, checkPath) {
+ if (routePath.length !== checkPath.length) {
return false;
}
- for (let i = 0; i < fixedPath.length; i++) {
- const fixedPart = fixedPath[i];
+ for (let i = 0; i < routePath.length; i++) {
+ const fixedPart = routePath[i];
const checkPart = checkPath[i];
- if (typeof fixedPart === 'object' && typeof checkPart === 'object') {
- if (fixedPart[PARAM] !== checkPart[PARAM]) {
+ if (fixedPart instanceof PathParameter && checkPart instanceof PathParameter) {
+ if (fixedPart.parameter !== checkPart.parameter) {
return false;
}
} else if (fixedPart !== checkPart) {
/**
- * Compare checkPath to fixedPath, populating params.
- * @param {Router~SearchPath} fixedPath
+ * Compare routePath to fixedPath, populating params.
+ * @param {Router~RoutePath} routePath
* @param {Array<String>} checkPath
* @param {Object} returnParams
* @returns {Boolean}
* @private
*/
- static _pathCompareParam(fixedPath, checkPath, returnParams = {}) {
+ static _pathCompareParam(routePath, checkPath, returnParams = {}) {
const params = {};
- if (fixedPath.length !== checkPath.length) {
+ if (routePath.length !== checkPath.length) {
return false;
}
- for (let i = 0; i < fixedPath.length; i++) {
- const fixedPart = fixedPath[i];
+ for (let i = 0; i < routePath.length; i++) {
+ const fixedPart = routePath[i];
const checkPart = checkPath[i];
- if (typeof fixedPart === 'object') {
- params[fixedPart[PARAM]] = checkPart;
+ if (fixedPart instanceof PathParameter) {
+ params[fixedPart.parameter] = checkPart;
} else if (fixedPart !== checkPart) {
return false;
}
/**
* @typedef Router~MatchedPath
* @property {Object} pathParams populated param fields
- * @property {Router~SearchPath} matchedPath
+ * @property {Router~RoutePath} matchedPath
*/
/**
* Search for an existing path, return matched path and path parameters.
/**
* Return a matching path, no param substitution, params must match
- * @param {Router~SearchPath} matchParts
- * @returns {Router~SearchPath=}
+ * @param {Router~RoutePath} routePath
+ * @returns {Router~RoutePath=}
* @private
*/
- _pathFindExact(matchParts) {
- const pathsByLength = this.pathsByLength[matchParts.length];
+ _pathFindExact(routePath) {
+ const pathsByLength = this.pathsByLength[routePath.length];
if (pathsByLength) {
for (const p of pathsByLength) {
- if (Router._pathCompareExact(p, matchParts)) {
+ if (Router._pathCompareExact(p, routePath)) {
return p;
}
}
* @param {any[]} handlerArgs
*/
on(methods, urlPath, handler, handlerArgs = []) {
- const matchParts = this._pathDefinitionToPathMatch(urlPath);
+ const matchParts = this._pathToRoutePath(urlPath);
let existingPath = this._pathFindExact(matchParts);
if (!existingPath) {
existingPath = matchParts;
if (!httpMethods.includes(method) && method !== '*') {
throw new DingusError(`invalid method '${method}'`);
}
- existingPath[METHODS][method] = { handler, handlerArgs };
+ existingPath[kPathMethods][method] = { handler, handlerArgs };
});
}
ctx.params = pathParams;
if (matchedPath) {
ctx.matchedPath = matchedPath;
- if (method in matchedPath[METHODS]) {
- return matchedPath[METHODS][method];
+ if (method in matchedPath[kPathMethods]) {
+ return matchedPath[kPathMethods][method];
}
- if ('*' in matchedPath[METHODS]) {
- return matchedPath[METHODS]['*'];
+ if ('*' in matchedPath[kPathMethods]) {
+ return matchedPath[kPathMethods]['*'];
}
throw new DingusError('NoMethod');
}
}
+Router.kPathMethods = kPathMethods;
module.exports = Router;
const assert = require('assert');
const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
const Router = require('../../lib/router');
+const PathParameter = require('../../lib/router/path-parameter')
const { DingusError } = require('../../lib/errors');
const noExpectedException = 'did not get expected exception';
router.ignoreTrailingSlash = _its;
});
- describe('_pathDefinitionToPathMatch', function () {
+ describe('_pathToRoutePath', function () {
it('defines a simple path', function () {
const p = '/a/b/c';
const expected = ['', 'a', 'b', 'c'];
- expected[router.METHODS] = {};
- const r = router._pathDefinitionToPathMatch(p);
+ expected[Router.kPathMethods] = {};
+ const r = router._pathToRoutePath(p);
assert.deepStrictEqual(r, expected);
});
it('defines a path with parameter', function () {
const p = '/a/b/:c';
- const expected = ['', 'a', 'b', { [router.PARAM]: 'c' }];
- expected[router.METHODS] = {};
- const r = router._pathDefinitionToPathMatch(p);
+ const expected = ['', 'a', 'b', new PathParameter('c')];
+ expected[Router.kPathMethods] = {};
+ const r = router._pathToRoutePath(p);
assert.deepStrictEqual(r, expected);
});
it('defines a path with trailing slash', function () {
router.ignoreTrailingSlash = true;
const p = '/a/b/:c/';
- const expected = ['', 'a', 'b', { [router.PARAM]: 'c' }];
- expected[router.METHODS] = {};
- const r = router._pathDefinitionToPathMatch(p);
+ const expected = ['', 'a', 'b', new PathParameter('c')];
+ expected[Router.kPathMethods] = {};
+ const r = router._pathToRoutePath(p);
assert.deepStrictEqual(r, expected);
});
- }); // _pathDefinitionToPathMatch
+ }); // _pathToRoutePath
describe('_pathCompareExact', function () {
let fixedPath, checkPath;
it('compares static paths which match', function () {
- fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/b/c');
+ fixedPath = router._pathToRoutePath('/a/b/c');
+ checkPath = router._pathToRoutePath('/a/b/c');
const r = Router._pathCompareExact(fixedPath, checkPath);
assert.strictEqual(r, true);
});
it('compares static paths which do not match', function () {
- fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/b/d');
+ fixedPath = router._pathToRoutePath('/a/b/c');
+ checkPath = router._pathToRoutePath('/a/b/d');
const r = Router._pathCompareExact(fixedPath, checkPath);
assert.strictEqual(r, false);
});
it('compares unequal static paths', function () {
- fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/b');
+ fixedPath = router._pathToRoutePath('/a/b/c');
+ checkPath = router._pathToRoutePath('/a/b');
const r = Router._pathCompareExact(fixedPath, checkPath);
assert.strictEqual(r, false);
});
it('compares param paths which match', function () {
- fixedPath = router._pathDefinitionToPathMatch('/a/:b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/:b/c');
+ fixedPath = router._pathToRoutePath('/a/:b/c');
+ checkPath = router._pathToRoutePath('/a/:b/c');
const r = Router._pathCompareExact(fixedPath, checkPath);
assert.strictEqual(r, true);
});
it('compares param paths which do not match', function () {
- fixedPath = router._pathDefinitionToPathMatch('/a/:b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/:g/c');
+ fixedPath = router._pathToRoutePath('/a/:b/c');
+ checkPath = router._pathToRoutePath('/a/:g/c');
const r = Router._pathCompareExact(fixedPath, checkPath);
assert.strictEqual(r, false);
});
it('compares static paths which match', function () {
const params = {};
const expectedParams = {};
- fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/b/c');
+ fixedPath = router._pathToRoutePath('/a/b/c');
+ checkPath = router._pathToRoutePath('/a/b/c');
const r = Router._pathCompareParam(fixedPath, checkPath);
assert.strictEqual(r, true);
assert.deepStrictEqual(params, expectedParams);
it('compares static paths which do not match', function () {
const params = {};
const expectedParams = {};
- fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/b/d');
+ fixedPath = router._pathToRoutePath('/a/b/c');
+ checkPath = router._pathToRoutePath('/a/b/d');
const r = Router._pathCompareParam(fixedPath, checkPath, params);
assert.strictEqual(r, false);
assert.deepStrictEqual(params, expectedParams);
it('compares unequal static paths', function () {
const params = {};
const expectedParams = {};
- fixedPath = router._pathDefinitionToPathMatch('/a/b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/b');
+ fixedPath = router._pathToRoutePath('/a/b/c');
+ checkPath = router._pathToRoutePath('/a/b');
const r = Router._pathCompareParam(fixedPath, checkPath, params);
assert.strictEqual(r, false);
assert.deepStrictEqual(params, expectedParams);
const expectedParams = {
b: 'bar',
};
- fixedPath = router._pathDefinitionToPathMatch('/a/:b/c');
- checkPath = router._pathDefinitionToPathMatch('/a/bar/c');
+ fixedPath = router._pathToRoutePath('/a/:b/c');
+ checkPath = router._pathToRoutePath('/a/bar/c');
const r = Router._pathCompareParam(fixedPath, checkPath, params);
assert.strictEqual(r, true);
assert.deepStrictEqual(params, expectedParams);
b: 'gaz',
c: '123',
};
- fixedPath = router._pathDefinitionToPathMatch('/a/:b/:c');
- checkPath = router._pathDefinitionToPathMatch('/a/gaz/123');
+ fixedPath = router._pathToRoutePath('/a/:b/:c');
+ checkPath = router._pathToRoutePath('/a/gaz/123');
const r = Router._pathCompareParam(fixedPath, checkPath, params);
assert.strictEqual(r, true);
assert.deepStrictEqual(params, expectedParams);
beforeEach(function () {
pathsByLengthOrig = router.pathsByLength;
router.pathsByLength = {
- 2: [ router._pathDefinitionToPathMatch('/:id') ],
- 3: [ router._pathDefinitionToPathMatch('/a/b') ],
+ 2: [ router._pathToRoutePath('/:id') ],
+ 3: [ router._pathToRoutePath('/a/b') ],
};
});
afterEach(function () {
describe('_pathFindExact', function () {
it('finds a path', function () {
- const pathParts = ['', { [router.PARAM]: 'id' }];
+ const pathParts = ['', new PathParameter('id')];
const r = router._pathFindExact(pathParts);
assert.strictEqual(r, router.pathsByLength[2][0]);
});
beforeEach(function () {
pathsByLengthOrig = router.pathsByLength;
router.pathsByLength = {
- 2: [ router._pathDefinitionToPathMatch('/:id') ],
+ 2: [ router._pathToRoutePath('/:id') ],
};
});
afterEach(function () {
it('adds new path', function () {
const urlPath = '/a/:id';
- const expected = router._pathDefinitionToPathMatch(urlPath);
- expected[router.METHODS]['GET'] = stubEntry;
+ const expected = router._pathToRoutePath(urlPath);
+ expected[Router.kPathMethods]['GET'] = stubEntry;
router.on('GET', urlPath, stubHandler);
assert.deepStrictEqual(router.pathsByLength[3][0], expected);
});
it('adds new method to path', function () {
const urlPath = '/a/:id';
- const expected = router._pathDefinitionToPathMatch(urlPath);
- expected[router.METHODS]['GET'] = stubEntry;
- expected[router.METHODS]['POST'] = stubEntry;
+ const expected = router._pathToRoutePath(urlPath);
+ expected[Router.kPathMethods]['GET'] = stubEntry;
+ expected[Router.kPathMethods]['POST'] = stubEntry;
router.on('GET', urlPath, stubHandler);
router.on('POST', urlPath, stubHandler);
it('add some more paths', function () {
let urlPath = '/a/b/c/d';
- const expected = router._pathDefinitionToPathMatch(urlPath);
- expected[router.METHODS]['GET'] = stubEntry;
+ const expected = router._pathToRoutePath(urlPath);
+ expected[Router.kPathMethods]['GET'] = stubEntry;
router.on('GET', urlPath, stubHandler);
urlPath = '/a/b/x/y';
router.on('GET', urlPath, stubHandler);
it('adds multiple methods', function () {
const urlPath = '/:id';
- const expected = router._pathDefinitionToPathMatch(urlPath);
- expected[router.METHODS]['GET'] = stubEntry;
- expected[router.METHODS]['HEAD'] = stubEntry;
+ const expected = router._pathToRoutePath(urlPath);
+ expected[Router.kPathMethods]['GET'] = stubEntry;
+ expected[Router.kPathMethods]['HEAD'] = stubEntry;
router.on(['GET', 'HEAD'], urlPath, stubHandler);
assert.deepStrictEqual(router.pathsByLength[2][0], expected);
it('adds new wildcard path', function () {
const urlPath = '/a/:id';
- const expected = router._pathDefinitionToPathMatch(urlPath);
- expected[router.METHODS]['*'] = stubEntry;
+ const expected = router._pathToRoutePath(urlPath);
+ expected[Router.kPathMethods]['*'] = stubEntry;
router.on('*', urlPath, stubHandler);
assert.deepStrictEqual(router.pathsByLength[3][0], expected);
});