minor refactoring in router, clarifying names and complicating parameter objects
[squeep-api-dingus] / lib / router / index.js
similarity index 67%
rename from lib/router.js
rename to lib/router/index.js
index 2bad3e2b509373d22279d6a866f1dbd1e57a11c8..a13ac47d62f083e588d047f7d556f7502f4b91e4 100644 (file)
@@ -6,12 +6,12 @@
  */
 
 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,
@@ -30,8 +30,6 @@ const defaultOptions = {
  * 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 {
   /**
@@ -46,57 +44,50 @@ 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) {
@@ -108,24 +99,24 @@ class Router {
 
 
   /**
-   * 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;
       }
@@ -138,7 +129,7 @@ class Router {
   /**
    * @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.
@@ -166,15 +157,15 @@ class Router {
 
   /**
    * 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;
         }
       }
@@ -199,7 +190,7 @@ class Router {
    * @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;
@@ -218,7 +209,7 @@ class Router {
       if (!httpMethods.includes(method) && method !== '*') {
         throw new DingusError(`invalid method '${method}'`);
       }
-      existingPath[METHODS][method] = { handler, handlerArgs };
+      existingPath[kPathMethods][method] = { handler, handlerArgs };
     });
   }
 
@@ -243,11 +234,11 @@ class Router {
     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');
     }
@@ -257,5 +248,6 @@ class Router {
 
 
 }
+Router.kPathMethods = kPathMethods;
 
 module.exports = Router;