*/
const { METHODS: httpMethods } = require('node:http');
-const common = require('../common');
+const { setOptions } = require('../common');
const { DingusError, RouterNoPathError, RouterNoMethodError } = require('../errors');
const PathParameter = require('./path-parameter');
-// Internal identifiers for route entries.
-const kPathMethods = Symbol('kSqueepDingusRouterPathMethods');
-const kPathName = Symbol('kSqueepDingusRouterPathName');
-
-const defaultOptions = {
- ignoreTrailingSlash: false,
- paramPrefix: ':',
-};
-
/**
* @typedef {Array<string|PathParameter>} RoutePath
* @property {{[method: string]: PathHandler}} kPathMethods (symbol key)
* mapping of methods to handler functions.
*/
class Router {
- static kPathMethods = kPathMethods;
- static kPathName = kPathName;
+ // Internal identifiers for route entries.
+ static kPathMethods = Symbol('kSqueepDingusRouterPathMethods');
+ static kPathName = Symbol('kSqueepDingusRouterPathName');
+
+ static defaultOptions = {
+ ignoreTrailingSlash: false,
+ paramPrefix: ':',
+ };
+
+ ignoreTrailingSlash;
+ paramPrefix;
pathsByLength;
+ pathsByName;
/**
* @param {object} options options object
* @param {string} options.paramPrefix prefix of a path part denoting a named parameter when registering paths (default: ':')
*/
constructor(options = {}) {
- common.setOptions(this, defaultOptions, options);
+ setOptions(this, this.constructor.defaultOptions, options);
/**
* Keep lists of registered paths to match, indexed by number of path parts.
.map((p) => this._pathPartMunge(p));
if (this.ignoreTrailingSlash
- && routePath[routePath.length - 1] === '') {
+ && (routePath[routePath.length - 1] === '')) {
routePath.pop();
}
- routePath[kPathMethods] = {};
+ routePath[this.constructor.kPathMethods] = {};
if (name) {
- routePath[kPathName] = name;
+ routePath[this.constructor.kPathName] = name;
}
Object.defineProperty(routePath, 'path', {
/**
* Compare checkPath to fixedPath, no param substitution, params must match.
+ * Used when adding routes to find existing.
* @param {RoutePath} routePath route path
* @param {RoutePath} checkPath path to match
* @returns {boolean} matched
for (let i = 0; i < routePath.length; i++) {
const fixedPart = routePath[i];
const checkPart = checkPath[i];
- if (fixedPart instanceof PathParameter && checkPart instanceof PathParameter) {
+ if ((fixedPart instanceof PathParameter) && (checkPart instanceof PathParameter)) {
if (fixedPart.parameter !== checkPart.parameter) {
return false;
}
* Compare routePath to fixedPath, populating params.
* @param {RoutePath} routePath route path
* @param {Array<string>} checkPath request path to check
- * @param {object} returnParams populated param components on match
- * @returns {boolean} matched
+ * @returns {[boolean, object]} [matched, params]
* @private
*/
- static _pathCompareParam(routePath, checkPath, returnParams = {}) {
+ static _pathCompareParam(routePath, checkPath) {
const params = {};
if (routePath.length !== checkPath.length) {
- return false;
+ return [false, undefined];
}
for (let i = 0; i < routePath.length; i++) {
const fixedPart = routePath[i];
if (fixedPart instanceof PathParameter) {
params[fixedPart.parameter] = checkPart;
} else if (fixedPart !== checkPart) {
- return false;
+ return [false, undefined];
}
}
- Object.assign(returnParams, params);
- return true;
+ return [true, params];
}
const pathsByLength = this.pathsByLength[matchParts.length];
if (pathsByLength) {
for (const p of pathsByLength) {
- if (Router._pathCompareParam(p, matchParts, result.pathParams)) {
+ const [isMatch, pathParams] = this.constructor._pathCompareParam(p, matchParts, result.pathParams);
+ if (isMatch) {
result.matchedPath = p;
+ result.pathParams = pathParams;
break;
}
- result.pathParams = {}; // Reset after potential population from failed match.
}
}
return result;
if (!Array.isArray(methods)) {
methods = [methods];
}
- if (typeof handlerArgs !== 'undefined' && !Array.isArray(handlerArgs)) {
+ if (((typeof handlerArgs) !== 'undefined') && (!Array.isArray(handlerArgs))) {
throw new TypeError(`handlerArgs must be an Array, not '${typeof handlerArgs}'`);
}
methods.forEach((method) => {
- if (!httpMethods.includes(method) && method !== '*') {
+ if ((!httpMethods.includes(method)) && (method !== '*')) {
throw new DingusError(`invalid method '${method}'`);
}
- existingPath[kPathMethods][method] = { handler, handlerArgs: handlerArgs ?? [] };
+ existingPath[this.constructor.kPathMethods][method] = { handler, handlerArgs: handlerArgs ?? [] };
});
}
lookup(method, urlPath, ctx = {}) {
const pathParts = urlPath.split('/').map((part) => decodeURIComponent(part));
if (this.ignoreTrailingSlash
- && pathParts[pathParts.length - 1] === '') {
+ && (pathParts[pathParts.length - 1] === '')) {
pathParts.pop();
}
const { matchedPath, pathParams } = this._pathFind(pathParts);
ctx.params = pathParams;
if (matchedPath) {
ctx.matchedPath = matchedPath.path;
- if (method in matchedPath[kPathMethods]) {
- return matchedPath[kPathMethods][method];
+ if (method in matchedPath[this.constructor.kPathMethods]) {
+ return matchedPath[this.constructor.kPathMethods][method];
}
- if ('*' in matchedPath[kPathMethods]) {
- return matchedPath[kPathMethods]['*'];
+ if ('*' in matchedPath[this.constructor.kPathMethods]) {
+ return matchedPath[this.constructor.kPathMethods]['*'];
}
- throw new RouterNoMethodError(Object.keys(matchedPath[kPathMethods]));
+ throw new RouterNoMethodError(Object.keys(matchedPath[this.constructor.kPathMethods]));
}
ctx.unmatchedPath = pathParts;
throw new RouterNoPathError();
let fixedPath, checkPath;
it('compares static paths which match', function () {
- const params = {};
const expectedParams = {};
fixedPath = router._pathToRoutePath('/a/b/c');
checkPath = router._pathToRoutePath('/a/b/c');
- const r = Router._pathCompareParam(fixedPath, checkPath);
+ const [r, params] = 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 = {};
+ const expectedParams = undefined;
fixedPath = router._pathToRoutePath('/a/b/c');
checkPath = router._pathToRoutePath('/a/b/d');
- const r = Router._pathCompareParam(fixedPath, checkPath, params);
+ const [r, params] = Router._pathCompareParam(fixedPath, checkPath);
assert.strictEqual(r, false);
- assert.deepStrictEqual(params, expectedParams);
+ assert.strictEqual(params, expectedParams);
});
it('compares unequal static paths', function () {
- const params = {};
- const expectedParams = {};
+ const expectedParams = undefined;
fixedPath = router._pathToRoutePath('/a/b/c');
checkPath = router._pathToRoutePath('/a/b');
- const r = Router._pathCompareParam(fixedPath, checkPath, params);
+ const [r, params] = Router._pathCompareParam(fixedPath, checkPath);
assert.strictEqual(r, false);
assert.deepStrictEqual(params, expectedParams);
});
it('compares param paths which match', function () {
- const params = {};
const expectedParams = {
b: 'bar',
};
fixedPath = router._pathToRoutePath('/a/:b/c');
checkPath = router._pathToRoutePath('/a/bar/c');
- const r = Router._pathCompareParam(fixedPath, checkPath, params);
+ const [r, params] = Router._pathCompareParam(fixedPath, checkPath);
assert.strictEqual(r, true);
assert.deepStrictEqual(params, expectedParams);
});
it('compares multi param path which match', function () {
- const params = {};
const expectedParams = {
b: 'gaz',
c: '123',
};
fixedPath = router._pathToRoutePath('/a/:b/:c');
checkPath = router._pathToRoutePath('/a/gaz/123');
- const r = Router._pathCompareParam(fixedPath, checkPath, params);
+ const [r, params] = Router._pathCompareParam(fixedPath, checkPath);
assert.strictEqual(r, true);
assert.deepStrictEqual(params, expectedParams);
});