router_Route.js

/** @module router/Route */

/**
 *  lambda-lambda-lambda/router
 *  AWS Lambda@Edge serverless application router.
 *
 *  Copyright 2021-2023, Marc S. Brooks (https://mbrooks.info)
 *  Licensed under the MIT license:
 *  http://www.opensource.org/licenses/mit-license.php
 */

'use strict';

// Local modules.
const {getResourceId, isAsyncFunc, isValidFunc, setFuncName} = require('./Utils');

/**
 * Handle routing operations for the given Route.
 *
 * @example
 * // .. sam-app/src/routes/foo.js
 * const routeConfig = {
 *   middleware: [middlewareFunc1, middlewareFunc2],
 *   resource: true || ['get', 'put', 'patch', 'submit'],
 *
 *   index  (req, res) {},
 *   get    (req, res, id) {},
 *   create (req, res) {},
 *   put    (req, res, id) {},
 *   update (req, res) {},
 *   patch  (req, res, id) {},
 *   post   (req, res) {},
 *   submit (req, res, id) {},
 *   delete (req, res, id) {}
 * };
 *
 * module.exports = routeConfig;
 */
module.exports = (router, route) => {
  const path = route.path;

  const methodMap = {
    index: 'get',
    create: 'put',
    update: 'patch',
    delete: 'delete',
    submit: 'post'
  };

  const isResource = !!route.resource;

  const uriBase = `${router.prefix}${path}`;
  const uriPath = getResourceId(router.req.uri(), uriBase);
  const reqPath = `${uriBase}/${uriPath}`;

  // Ensure route matches request URI, skip otherwise.
  if ((!uriPath && !isResource && uriBase !== router.req.uri()) || (uriPath && isResource && reqPath !== router.req.uri())) {
    return;
  }

  // Add route-bound middleware, if available.
  if (route.middleware) {
    if (Array.isArray(route.middleware)) {
      route.middleware.forEach(func => router.use(path, func));
    } else {
      router.use(path, route.middleware);
    }
  }

  // Map Route-defined action -> Request method.
  for (let key in route) {
    const method = methodMap[key] || key;

    if (!isValidFunc(router[method]) || !isValidFunc(route[key])) {
      continue;
    }

    let routeFunc = route[key];
    let entityType = 'route';

    // Support - Boolean | Array<methodMapKey>
    if (isResource && (route.resource === true || (Array.isArray(route.resource) && route.resource.includes(key)))) {
      const resourceId = uriPath;

      // Add resource ID to Route as argument.
      const oldFunc = routeFunc;

      if (isAsyncFunc(oldFunc)) {

        // Asynchronous handling.
        routeFunc = async (req, res) => {
          return await oldFunc(req, res, resourceId);
        };
      } else {

        // Synchronous handling.
        routeFunc = (req, res) => {
          return oldFunc(req, res, resourceId);
        };
      }

      // .. send error response.
      if (!resourceId) {
        routeFunc = (req, res) => {
          res.setHeader('Cache-Control', 'max-age=0');
          res.status(404).send();
        };
      }

      entityType = 'resource';
    }

    setFuncName(routeFunc, `${entityType}:${key}`);

    // Execute the route-defined function.
    router[method](path, routeFunc);
  }
};