minor documentation updates
[squeep-api-dingus] / lib / router.js
index 61a8d11cb3e55e2f4d434940166a4027f0b2ab51..2bad3e2b509373d22279d6a866f1dbd1e57a11c8 100644 (file)
@@ -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<String|Router~ParamPart>} 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<String>} 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<String>} 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));