fix(api-handler): widen safeRequestProp narrow-catch to tolerate undefined requests

Follow-up to commit `61d156e7`. The narrow catch initially recognised
only the V8 private-field TypeError wordings, but unit tests routinely
invoke route handlers as `GET()` with no arguments — the wrapper then
reads `args[0]` as `undefined` and the read callback raises
`TypeError: Cannot read properties of undefined (reading 'url')`,
which is not a private-field error.

The classifier is renamed to `isTolerableRequestProbeError` and now
also matches the "cannot read properties of undefined / null"
TypeError shape (both modern V8 wording and the older
`Cannot read property 'X' of undefined` form). Any other TypeError
still rethrows so production regressions stay visible.

This restores the no-arg-direct-invoke contract that
`src/app/api/auth/me/source-priority/__tests__/route.test.ts` and
similar vitest suites rely on (`GET()` with no request to assert
the auth-gate path returns 401).

Verification:
  pnpm typecheck                                       → clean
  pnpm vitest run api-handler-safe-request-prop        → 11 / 11 pass
  pnpm vitest run src/app/api/auth/me/source-priority  → 6 / 6 pass
  pnpm vitest run (full unit)                          → 3751 / 3751
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 21:50:29 +02:00
1 parent bbc0afd555
commit fcda115123
2 files changed
+57 -21

No files matched your search

+21 -7
View File
@@ -41,18 +41,32 @@ export class HttpError extends Error {
// and fall back to safe defaults so logging instrumentation never
// crashes the handler.
//
// The catch is narrowed to the V8 private-field TypeError. Any other
// The catch is narrowed to two well-known shapes: the V8 private-field
// TypeError, and the `Cannot read properties of undefined/null` shape
// raised when the wrapper is invoked without a request (vitest tests
// frequently invoke handlers as `GET()` with no args, mirroring the
// shape Next.js exercises for the static-export pass). Any other
// exception is a real bug in the read callback or in a downstream
// header parser and must surface — swallowing it here would hide
// header parser and must surface — swallowing it would hide
// regressions in production instrumentation.
function isPrivateFieldAccessError(err: unknown): boolean {
function isTolerableRequestProbeError(err: unknown): boolean {
if (!(err instanceof TypeError)) return false;
const msg = err.message ?? "";
return (
// V8 — current
msg.includes("private member") ||
// V8 — alternative
msg.includes("private field") ||
// Bun / older V8 wording variant
msg.includes("private name")
// Bun / older V8
msg.includes("private name") ||
// No request handed in at all (vitest direct-invoke or
// force-static placeholder reduced to undefined / null).
// Covers both modern `Cannot read properties of undefined
// (reading 'X')` and the older `Cannot read property 'X' of
// undefined` wordings.
/Cannot read propert(?:y|ies)\b.*\bof (?:undefined|null)\b/.test(
msg,
)
);
}
@@ -64,7 +78,7 @@ function safeRequestProp<R>(
try {
return read(request as NextRequest);
} catch (err) {
if (isPrivateFieldAccessError(err)) return fallback;
if (isTolerableRequestProbeError(err)) return fallback;
throw err;
}
}
@@ -72,7 +86,7 @@ function safeRequestProp<R>(
/** @internal — exposed for unit tests of the narrow-catch contract. */
export const __testables = {
safeRequestProp,
isPrivateFieldAccessError,
isTolerableRequestProbeError,
};
// Next.js route handlers come in two shapes — `(request)` for static routes