X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=lib%2Frouter%2Findex.js;h=fd19bdf5192cfd9efd7b4c00023f1ae92265cbd1;hb=refs%2Fheads%2Fmaster;hp=1585065e14ce79b804cd6de967965951ab0d535a;hpb=5c9113409d3b48549f46f211cd24a39b12118e1a;p=squeep-api-dingus diff --git a/lib/router/index.js b/lib/router/index.js index 1585065..975187f 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -5,9 +5,9 @@ * A very simple router. */ -const { METHODS: httpMethods } = require('http'); +const { METHODS: httpMethods } = require('node:http'); const common = require('../common'); -const { DingusError } = require('../errors'); +const { DingusError, RouterNoPathError, RouterNoMethodError } = require('../errors'); const PathParameter = require('./path-parameter'); // Internal identifiers for route entries. @@ -18,6 +18,24 @@ const defaultOptions = { paramPrefix: ':', }; +/** + * @typedef {Array} RoutePath + * @property {{[method: string]: PathHandler}} kPathMethods (symbol key) + */ +/** + * @callback HandlerFn + */ +/** + * @typedef {object} PathHandler + * @property {HandlerFn} handler invoked on path match + * @property {any[]} handlerArgs passed to handler + */ +/** + * @typedef MatchedPath + * @property {object} pathParams populated param fields + * @property {RoutePath=} matchedPath matched path + */ + /** * A naïve router which maps incoming requests to handler functions * by way of url path and request method. @@ -28,34 +46,34 @@ const defaultOptions = { * of its constituent parts as strings or objects, for invariant or * parameterized parts respectively. Each search path is assigned a * mapping of methods to handler functions. - * - * @property {Object} pathsByLength index to registered paths by number of parts */ class Router { + static kPathMethods = kPathMethods; + pathsByLength; + /** - * @param {Object} options - * @param {Boolean} options.ignoreTrailingSlash discard any trailing slashes when registering and comparing paths (default: false) - * @param {String} options.paramPrefix prefix of a path part denoting a named parameter when registering paths (default: ':') + * @param {object} options options object + * @param {boolean} options.ignoreTrailingSlash discard any trailing slashes when registering and comparing paths (default: false) + * @param {string} options.paramPrefix prefix of a path part denoting a named parameter when registering paths (default: ':') */ constructor(options = {}) { common.setOptions(this, defaultOptions, options); - // Keep lists of paths to match, indexed by path length. + /** + * Keep lists of registered paths to match, indexed by number of path parts. + * @type {{[length: number]: RoutePath[]}} + */ this.pathsByLength = { 1: [], }; } - /** - * @typedef {Array} Router~RoutePath - * @property {Object} kPathMethods (symbol key) - */ /** * Prepare a path for insertion into search list. * 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} + * @param {string} rawPath path string + * @returns {RoutePath} route path * @private */ _pathToRoutePath(rawPath) { @@ -80,11 +98,11 @@ class Router { } - /* + /** * Convert a path part string to parameter if needed. * Remove escape from an escaped parameter. - * @param {String} part - * @returns {PathParameter|String} + * @param {string} part component of a path + * @returns {PathParameter|string} component or parameter object * @private */ _pathPartMunge(part) { @@ -100,9 +118,9 @@ class Router { /** * Compare checkPath to fixedPath, no param substitution, params must match. - * @param {Router~RoutePath} routePath - * @param {Router~RoutePath} checkPath - * @returns {Boolean} + * @param {RoutePath} routePath route path + * @param {RoutePath} checkPath path to match + * @returns {boolean} matched * @private */ static _pathCompareExact(routePath, checkPath) { @@ -126,10 +144,10 @@ class Router { /** * Compare routePath to fixedPath, populating params. - * @param {Router~RoutePath} routePath - * @param {Array} checkPath - * @param {Object} returnParams - * @returns {Boolean} + * @param {RoutePath} routePath route path + * @param {Array} checkPath request path to check + * @param {object} returnParams populated param components on match + * @returns {boolean} matched * @private */ static _pathCompareParam(routePath, checkPath, returnParams = {}) { @@ -152,15 +170,10 @@ class Router { } - /** - * @typedef Router~MatchedPath - * @property {Object} pathParams populated param fields - * @property {Router~RoutePath} matchedPath - */ /** * Search for an existing path, return matched path and path parameters. - * @param {Array} matchParts - * @returns {Router~MatchedPath} + * @param {Array} matchParts path components + * @returns {MatchedPath} matched or not * @private */ _pathFind(matchParts) { @@ -175,6 +188,7 @@ class Router { result.matchedPath = p; break; } + result.pathParams = {}; // Reset after potential population from failed match. } } return result; @@ -183,8 +197,8 @@ class Router { /** * Return a matching path, no param substitution, params must match - * @param {Router~RoutePath} routePath - * @returns {Router~RoutePath=} + * @param {RoutePath} routePath path + * @returns {RoutePath=} matched * @private */ _pathFindExact(routePath) { @@ -200,20 +214,12 @@ class Router { } - /** - * @callback Router~HandlerFn - */ - /** - * @typedef {Object} Router~PathHandler - * @property {Router~HandlerFn} handler - * @property {any[]} handlerArgs - */ /** * Insert a new path handler. - * @param {string|string[]} methods - * @param {string} urlPath - * @param {Router~HandlerFn} handler - * @param {any[]} handlerArgs + * @param {string|string[]} methods methods to match for this handler, '*' allowed + * @param {string} urlPath request path to match + * @param {HandlerFn} handler handler to invoke on match + * @param {any[]} handlerArgs additional arguments for handler */ on(methods, urlPath, handler, handlerArgs = []) { const matchParts = this._pathToRoutePath(urlPath); @@ -245,10 +251,10 @@ class Router { * arguments, for a requested url. * Also sets path named-parameters as #params and the matched path as * #matchedPath on the context. - * @param {string} method - * @param {string[]} urlPath - * @param {object} ctx - * @returns {Router~PathHandler} + * @param {string} method request method + * @param {string[]} urlPath request path + * @param {object} ctx request context, updated with matched metadata + * @returns {PathHandler} matched path */ lookup(method, urlPath, ctx = {}) { const pathParts = urlPath.split('/').map((part) => decodeURIComponent(part)); @@ -266,14 +272,13 @@ class Router { if ('*' in matchedPath[kPathMethods]) { return matchedPath[kPathMethods]['*']; } - throw new DingusError('NoMethod'); + throw new RouterNoMethodError(); } ctx.unmatchedPath = pathParts; - throw new DingusError('NoPath'); + throw new RouterNoPathError(); } } -Router.kPathMethods = kPathMethods; module.exports = Router;