Source: router.js

/**
 * Route registry backing automatic `HEAD`, automatic `OPTIONS`, `405 Method Not
 * Allowed` and the service description (specification §4.1 and §9).
 *
 * The registry keeps its own matcher rather than reading Express internals, so it
 * stays valid across Express versions.
 */

/** Methods that never need to be registered explicitly. */
export const IMPLICIT_METHODS = ["HEAD", "OPTIONS"];

/**
 * Compile an Express-style path pattern into a matcher.
 *
 * Supports `:param` segments and a trailing `*` wildcard, which is the subset the
 * registry needs in order to answer "does any route own this path?".
 *
 * @param {string} pattern - Path pattern
 * @returns {RegExp} Matcher anchored to the whole path
 */
export function compilePathPattern(pattern) {
  const source = pattern
    .split("/")
    .map((segment) => {
      if (segment.startsWith(":")) {
        return "[^/]+";
      }
      if (segment === "*" || segment.startsWith("*")) {
        return ".*";
      }
      return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    })
    .join("/");
  return new RegExp(`^${source}/?$`);
}

/**
 * The set of routes registered on an application.
 */
export class RouteTable {
  constructor() {
    /** @type {Map<string, {pattern: string, matcher: RegExp, methods: Map<string, object>}>} */
    this.routes = new Map();
  }

  /**
   * Register a method on a path.
   *
   * @param {string} method - HTTP method
   * @param {string} pattern - Path pattern
   * @param {object} [meta] - Description metadata for §9
   * @returns {void}
   */
  register(method, pattern, meta = {}) {
    let entry = this.routes.get(pattern);
    if (!entry) {
      entry = {
        pattern,
        matcher: compilePathPattern(pattern),
        methods: new Map(),
      };
      this.routes.set(pattern, entry);
    }
    entry.methods.set(method.toUpperCase(), meta);
  }

  /**
   * Find the route entry owning a concrete path.
   *
   * @param {string} pathname - Request path
   * @returns {object|undefined} Route entry, or undefined when unowned
   */
  find(pathname) {
    for (const entry of this.routes.values()) {
      if (entry.matcher.test(pathname)) {
        return entry;
      }
    }
    return undefined;
  }

  /**
   * List the methods allowed on a concrete path.
   *
   * `OPTIONS` is always allowed, and `HEAD` is allowed wherever `GET` is.
   *
   * @param {string} pathname - Request path
   * @returns {string[]|null} Allowed methods, or null when the path is unowned
   */
  allowedMethods(pathname) {
    const entry = this.find(pathname);
    if (!entry) {
      return null;
    }
    const methods = new Set(entry.methods.keys());
    if (methods.has("GET")) {
      methods.add("HEAD");
    }
    methods.add("OPTIONS");
    return [...methods].sort();
  }

  /**
   * Render the registry as the `routes` member of a service description.
   *
   * @returns {Array<{path: string, methods: string[], summary: string}>} Route descriptions
   */
  describe() {
    return [...this.routes.values()]
      .map((entry) => {
        const methods = this.allowedMethods(entry.pattern) ?? [];
        const summary = [...entry.methods.values()].find(
          (meta) => meta?.summary,
        )?.summary;
        const description = { path: entry.pattern, methods };
        if (summary) {
          description.summary = summary;
        }
        return description;
      })
      .sort((left, right) => left.path.localeCompare(right.path));
  }
}