X-Git-Url: http://git.squeep.com/?p=squeep-api-dingus;a=blobdiff_plain;f=lib%2Frouter.js;h=2bad3e2b509373d22279d6a866f1dbd1e57a11c8;hp=61a8d11cb3e55e2f4d434940166a4027f0b2ab51;hb=aee1a77b22217893a609e581de7e4d3521e2c54e;hpb=12568946a94e853c3c16974d57dd34de1ad3877c diff --git a/lib/router.js b/lib/router.js index 61a8d11..2bad3e2 100644 --- a/lib/router.js +++ b/lib/router.js @@ -18,11 +18,26 @@ const defaultOptions = { paramPrefix: ':', }; +/** + * A naïve router which maps incoming requests to handler functions + * by way of url path and request method. + * + * Regex parsing of paths was eschewed, as a design decision. + * + * Instead, each path to be searched for is deconstructed into a list + * 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 + * @property {Symbol} METHODS key to method:handler map on search paths + * @property {Symbol} PARAM key to parameter name in search path parts + */ class Router { /** * @param {Object} options - * @param {Boolean} options.ignoreTrailingSlash - * @param {Boolean} options.paramPrefix + * @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); @@ -36,14 +51,25 @@ class Router { this.PARAM = PARAM; } - + /** + * @typedef {Object} Router~ParamPart + * @property {String} PARAM (symbol key) + */ + /** + * @typedef {Array} Router~SearchPath + * @property {Object} METHODS (symbol key) + */ /** * Prepare a path for insertion into search list. - * A searchable path is a list of path parts, with a property of method handlers. - * @param {string} pathDefinition + * A searchable path is a list of path parts, with a property of an object of method handlers. + * @param {String} pathDefinition + * @returns {Router~SearchPath} + * @private */ _pathDefinitionToPathMatch(pathDefinition) { - const pathMatch = pathDefinition.split('/').map((p) => p.startsWith(this.paramPrefix) ? { [PARAM]: p.slice(this.paramPrefix.length) } : p); + const pathMatch = pathDefinition + .split('/') + .map((p) => p.startsWith(this.paramPrefix) ? { [PARAM]: p.slice(this.paramPrefix.length) } : p); if (this.ignoreTrailingSlash && pathMatch[pathMatch.length - 1] === '') { pathMatch.pop(); @@ -57,8 +83,10 @@ class Router { /** * Compare checkPath to fixedPath, no param substitution, params must match. - * @param {*} fixedPath - * @param {*} checkPath + * @param {Router~SearchPath} fixedPath + * @param {Router~SearchPath} checkPath + * @returns {Boolean} + * @private */ static _pathCompareExact(fixedPath, checkPath) { if (fixedPath.length !== checkPath.length) { @@ -81,9 +109,11 @@ class Router { /** * Compare checkPath to fixedPath, populating params. - * @param {*} fixedPath - * @param {*} checkPath - * @param {*} returnParams + * @param {Router~SearchPath} fixedPath + * @param {Array} checkPath + * @param {Object} returnParams + * @returns {Boolean} + * @private */ static _pathCompareParam(fixedPath, checkPath, returnParams = {}) { const params = {}; @@ -105,9 +135,16 @@ class Router { } + /** + * @typedef Router~MatchedPath + * @property {Object} pathParams populated param fields + * @property {Router~SearchPath} matchedPath + */ /** * Search for an existing path, return matched path and path parameters. - * @param {Array} matchParts + * @param {Array} matchParts + * @returns {Router~MatchedPath} + * @private */ _pathFind(matchParts) { const result = { @@ -129,7 +166,9 @@ class Router { /** * Return a matching path, no param substitution, params must match - * @param {*} matchParts + * @param {Router~SearchPath} matchParts + * @returns {Router~SearchPath=} + * @private */ _pathFindExact(matchParts) { const pathsByLength = this.pathsByLength[matchParts.length]; @@ -144,12 +183,20 @@ 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 {fn} handler - * @param {*[]} handlerArgs + * @param {Router~HandlerFn} handler + * @param {any[]} handlerArgs */ on(methods, urlPath, handler, handlerArgs = []) { const matchParts = this._pathDefinitionToPathMatch(urlPath); @@ -179,11 +226,12 @@ class Router { /** * Return an object, which contains a matching handler and any extra * arguments, for a requested url. - * Also sets path parameters on context. + * 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 {object} + * @returns {Router~PathHandler} */ lookup(method, urlPath, ctx = {}) { const pathParts = urlPath.split('/').map((part) => decodeURIComponent(part));