lib_shim_specs_middleware.js

/*
 * Copyright 2024 New Relic Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

'use strict'

const { ARG_INDEXES } = require('./constants')
const RecorderSpec = require('./recorder')

/**
 * Extracts the request object from the arguments to the middleware function.
 *
 * @typedef {function} RouteRequestFunction
 * @param {WebFrameworkShim} shim The shim used for instrumentation.
 * @param {Function} fn The middleware function.
 * @param {string} fnName The name of the middleware function.
 * @param {Array} args The arguments to the middleware function.
 * @returns {object} The request object.
 */

/**
 * Used to wrap functions that users can call to continue to the next
 * middleware.
 *
 * @typedef {function} RouteNextFunction
 * @param {WebFrameworkShim} shim The shim used for instrumentation.
 * @param {Function} fn The middleware function.
 * @param {string} fnName The name of the middleware function.
 * @param {Array} args The arguments to the middleware function.
 * @returns {object} The request object.
 */

/**
 * Extracts the route parameters from the arguments to the middleware function.
 *
 * @typedef {function} RouteParameterFunction
 * @param {WebFrameworkShim} shim The shim used for instrumentation.
 * @param {Function} fn The middleware function.
 * @param {string} fnName The name of the middleware function.
 * @param {Array} args The arguments to the middleware function.
 * @returns {object} A map of route parameter names to values.
 */

/* eslint-disable jsdoc/require-property-description */
/**
 * @typedef {object} MiddlewareSpecParams
 * @mixes RecorderSpecParams
 * @property {boolean} [appendPath]
 * @property {number|RouteNextFunction} [next]
 * @property {RouteParameterFunction|null} [params]
 * @property {number|RouteRequestFunction} [req]
 * @property {number} [res]
 * @property {number|string|null} [route]
 * @property {string} [type]
 */

/**
 * Spec that describes how to instrument a framework middleware function, e.g.
 * an `express` middleware.
 */
class MiddlewareSpec extends RecorderSpec {
  /**
   * Indicates if the route path for the middleware should be appended to
   * the transaction name or not.
   *
   * @type {boolean}
   */
  appendPath

  /**
   * When a number, indicates the argument position of the "next" callback in
   * the original middleware function's parameters list. Otherwise, it's a
   * function that will be invoked with the arguments of the middleware and
   * another function for wrapping calls that represent continuation from the
   * instrumented middleware.
   *
   * @type {number|RouteNextFunction}
   */
  next

  /**
   * A function to extract the route parameters from the instrumented
   * middleware's arguments list.
   *
   * @type {RouteParameterFunction}
   */
  params

  /**
   * When a number, indicates the argument position of the request object in
   * the middleware function's arguments list. Otherwise, it's a function that
   * extracts the request object from the middleware arguments.
   *
   * @type {number|RouteRequestFunction}
   */
  req

  /**
   * Indicates the argument position of the response object in the middleware
   * function's arguments list.
   *
   * @type {number}
   */
  res

  /**
   * When a number, indicates the argument position of the route string in the
   * middleware function's arguments list. Otherwise, it is a string that
   * represents the route path.
   *
   * @type {number|string|null}
   */
  route

  /**
   * Indicates the type of middleware that is being instrumented.
   *
   * @see {MiddlewareTypeNames}
   * @type {string}
   */
  type

  /* eslint-disable jsdoc/require-param-description */
  /**
   * @param {MiddlewareSpecParams} constructorParams
   */
  constructor(constructorParams) {
    super(constructorParams)

    this.appendPath = constructorParams.appendPath ?? true
    this.next = constructorParams.next ?? ARG_INDEXES.THIRD
    this.params =
      constructorParams.params ??
      function getParamsFromReq(...args) {
        // At some point in the future, after more inspection and wrapping
        // has been done, this function will be invoked with a potential
        // `req` object as the last parameters.
        // See https://github.com/newrelic/node-newrelic/blob/f33c0cc/lib/shim/webframework-shim/middleware.js#L69
        const req = args.at(-1)
        return req && req.params
      }
    this.req = constructorParams.req ?? ARG_INDEXES.FIRST
    this.res = constructorParams.res ?? ARG_INDEXES.SECOND
    this.route = constructorParams.route ?? null
    this.type = constructorParams.type ?? 'MIDDLEWARE'
  }
}

module.exports = MiddlewareSpec