All files / src/core/middleware get-applicable-middlewares.ts

100% Statements 26/26
100% Branches 20/20
100% Functions 4/4
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80                                              19x 19x 19x 12x 12x   7x                 26x     26x 15x 12x   7x       11x 1x     10x 10x   10x 7x 2x                   43x   43x 26x 20x       43x      
/**
 * Determines which middlewares are applicable to a route based on the pattern
 * Core logic - no dependencies
 */
 
import type { Middleware } from '../routing/route-types.js';
import type { MiddlewareInfo } from '../file-system/scan-api-dir.js';
 
/**
 * Information about a loaded middleware (with the middleware function already imported)
 */
export interface LoadedMiddleware {
  middleware: Middleware[];
  basePattern: string;
  /** Optional path matcher for global middleware (basePattern ''). Only routes matching one of these patterns get this middleware. E.g. ['protected/*', 'admin'] */
  pathPatterns?: string[];
}
 
/**
 * Returns true if routePattern matches a single path pattern.
 * Pattern may be exact ('admin') or prefix with * ('admin/*'). No leading slash.
 */
export function matchPathPattern(routePattern: string, configPattern: string): boolean {
  const route = routePattern.replace(/^\/+|\/+$/g, '');
  const pattern = configPattern.replace(/^\/+|\/+$/g, '');
  if (pattern.endsWith('/*')) {
    const prefix = pattern.slice(0, -2);
    return route === prefix || route.startsWith(prefix + '/');
  }
  return route === pattern || route.startsWith(pattern + '/');
}
 
/**
 * Checks if a middleware base pattern applies to a route pattern
 * A middleware applies if its basePattern is a prefix of the routePattern
 * For global middleware (basePattern '') with pathPatterns, applies only if route matches one of them.
 */
function isMiddlewareApplicable(loaded: LoadedMiddleware, routePattern: string): boolean {
  const { basePattern, pathPatterns } = loaded;
 
  // Global middleware (empty basePattern)
  if (basePattern === '') {
    if (pathPatterns != null && pathPatterns.length > 0) {
      return pathPatterns.some((p) => matchPathPattern(routePattern, p));
    }
    return true;
  }
 
  // If route is empty (root), only global middleware applies
  if (routePattern === '') {
    return false;
  }
 
  const normalizedBase = basePattern.replace(/^\/+|\/+$/g, '');
  const normalizedRoute = routePattern.replace(/^\/+|\/+$/g, '');
 
  if (normalizedRoute === normalizedBase) return true;
  if (normalizedRoute.startsWith(normalizedBase + '/')) return true;
  return false;
}
 
/**
 * Returns the middlewares applicable to a route, ordered from most generic to most specific
 */
export function getApplicableMiddlewares(
  middlewares: LoadedMiddleware[],
  routePattern: string
): Middleware[] {
  const applicable: Middleware[] = [];
 
  for (const loadedMiddleware of middlewares) {
    if (isMiddlewareApplicable(loadedMiddleware, routePattern)) {
      applicable.push(...loadedMiddleware.middleware);
    }
  }
 
  return applicable;
}