router_Utils.js

/** @module router/Utils */

/**
 *  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 reference.
const Utils = this;

/**
 * Check if value is an async function.
 *
 * @param {AsyncFunction} func
 *   Async function.
 *
 * @return {Boolean}
 *
 * @example
 * const result = isAsyncFunc(async function() {}));
 * // true
 */
exports.isAsyncFunc = function(value) {
  return (value && (value[Symbol.toStringTag] === 'AsyncFunction'));
};

/**
 * Check if value is an Object instance.
 *
 * @param {Object}
 *   Object to check.
 *
 * @return {Boolean}
 *
 * @example
 * const result = Utils.isObject({foo: 'bar'});
 * // true
 */
exports.isObject = function(value) {
  return typeof value === 'object' && Object.prototype.toString.call(value) === '[object Object]';
};

/**
 * Check if object is (or returns) Promise.
 *
 * @param {Object} obj
 *   Promise object.
 *
 * @return {Boolean}
 *
 * @example
 * const result = isPromise(new Promise(() => {}));
 * // true
 *
 * const result = function() {
 *   return Promise.resolve();
 * };
 * // true
 */
exports.isPromise = function(obj) {
  return (obj && (obj[Symbol.toStringTag] === 'Promise' || typeof obj.then === 'function'));
};

/**
 * Check if valid URI path.
 *
 * @param {String} value
 *   URI path value.
 *
 * @return {Boolean}
 *
 * @example
 * const result = isValidPath('/api/test');
 * // true
 */
exports.isValidPath = function(value) {
  return /^\/([a-z0-9-_\/]+)?$/.test(value);
};

/**
 * Check if valid Route/Middleware function.
 *
 * @param {Function} value
 *   Route function.
 *
 * @return {Boolean}
 *
 * @example
 * const result = isValidFunc((req, res, next) => {});
 * // true
 */
exports.isValidFunc = function(value) {
  return (typeof value === 'function' && value.length >= 1 && value.length <= 3);
};

/**
 * Check if valid Request/Route.
 *
 * @param {String} uri
 *   Request URI.
 *
 * @param {String} path
 *   Route path value.
 *
 * @param {Function} func
 *   Route function.
 *
 * @return {Boolean}
 *
 * @example
 * const result = isValidRoute('/api/test/123456', '/api/test', (req, res) => {}));
 * // true
 */
exports.isValidRoute = function(uri, path, func) {
  if (Utils.isValidFunc(func) && !/^route:/.test(func.name)) {
    return !!uri.match(new RegExp(`^${path}(\/[a-z0-9-_]+)?$`, 'i')); // nosemgrep
  }

  return (uri === path);
};

/**
 * Return URI fragment, if Route is a resource.
 *
 * @param {String} uri
 *   Request URI.
 *
 * @param {String} path
 *   Route path value.
 *
 * @return {String|undefined}
 *
 * @example
 * const result = getResourceId('/api/test/abc123', '/api/test');
 * // abc123
 */
exports.getResourceId = function(uri, path) {
  const fragment = uri.replace(new RegExp(`^(?:${path}(?:\/([a-z0-9-_]+))?)$`, 'i'), '$1'); // nosemgrep
  if (fragment !== uri) {
    return fragment;
  }
};

/**
 * Set name for a given function (inline).
 *
 * @param {Function} func
 *   Route function.
 *
 * @param {Function} value
 *   Function name.
 *
 * @example
 * const func = function() {};
 * setFuncName(func, 'test');
 * func.name
 * // test
 */
exports.setFuncName = function(func, value) {
  Object.defineProperty(func, 'name', {value});
};

/**
 * Get executed module parent directory.
 *
 * @return {String}
 *
 * @example
 * const dir = moduleParent();
 * // path/to/app/parent/directory
 */
exports.moduleParent = function() {
  const moduleParents = Object.values(require.cache)
    .filter((m) => m.children.includes(module));

  return moduleParents[0]?.parent.path;
};

/**
 * Execute events; propagate variable state.
 *
 * @param {Array} promises
 *   Array of promised events.
 *
 * @return {Promise}
 *
 * @example
 *   const state = {};
 *
 *   const promises = [
 *     ()  => Promise.resolve(a),
 *     (b) => Promise.resolve(b),
 *     (c) => Promise.resolve(c),
 *
 *     ..
 *   ];
 *
 *   promiseEvents(promises)
 *     .then(obj => obj)
 *     .catch(function(err) {
 *
 *     });
 */
exports.promiseEvents = function(promises) {
  return promises.reduce(function(current, next) {
    return current.then(next);
  }, Promise.resolve([]));
};