Surface silently-swallowed errors in zsa and zsa-openapi
## Summary
Audit of `zsa` and `zsa-openapi` turned up several spots where errors were silently
swallowed or coerced into misleading results. This PR makes those failure modes
observable without changing any documented happy-path behavior.
### `packages/zsa/src/zod-safe-function.ts`
- **`getRetryDelay` catch-all** (was `} catch { return -1 }`). A throwing user-supplied
`retry.delay` callback used to silently disable retries with zero diagnostic. Now
we log the secondary failure via `console.error` and still return `-1` so the
*original* handler error propagates through `handleError` (instead of the
delay-callback error replacing it).
- **`handleError` NEXT_REDIRECT check**. The old code did a raw `err.message ===`
comparison which throws a `TypeError` when a handler rejects with a primitive
(`throw "boom"`, `throw null`, etc.), replacing the original failure with a
confusing "Cannot read properties of undefined". Now guarded with an
`err && typeof err === "object" && "message" in err` check.
### `packages/zsa-openapi/src/openapi.ts`
- **Malformed request bodies** used to be swallowed in
`getDataFromRequest` (`catch (err) { data = undefined }`), meaning the action ran
with empty input and usually produced a confusing `INPUT_PARSE_ERROR`. Now a
genuinely malformed JSON/form body returns a proper `400 Invalid or malformed
request body`, while an *empty* body (common for `POST`s without a payload) is
still treated as "no input" so existing default-value actions keep working.
- **`shapeError` secondary failure** used to silently revert to the original error
without surfacing that the user's `shapeError` callback had itself thrown. Now
we `console.error` the secondary failure before falling back.
- **`parseRequest` catch-all** used to convert unexpected router/parsing failures
into a silent `404`. Now we log the error and return a `500` instead, so real
bugs show up in server logs and to clients.
- **Path-length mismatch** in `parseRequest` used to `return {} as any`, which let
a downstream `action(...)` call crash with a `TypeError: action is not a function`.
Now returns `null`, producing a clean `404`.
- **Content-type headers** added to the `415` and new `400` responses so clients can
parse the JSON error bodies.
### Tests
- `tests/jest/__tests__/openapi.test.tsx`: new tests for malformed JSON bodies
(`400`) and a throwing `shapeError` (falls back + logs).
- `tests/jest/__tests__/index.test.tsx` + `tests/jest/server/actions.ts`: new
`faultyRetryDelayAction` + assertion that the original handler error survives
a broken `retry.delay` callback.
- `tests/jest/lib/utils.ts`: `mockNextRequest` now exposes `.text()` to match the
real `NextRequest` API surface, since the new code peeks at the raw body before
JSON-parsing.
Full jest suite: `133 passed, 133 total`.
## Review & Testing Checklist for Human
- [ ] Confirm the new `400 Invalid or malformed request body` response is the
desired behaviour for your OpenAPI consumers. Previously malformed bodies were
silently coerced to empty input; callers that depended on that accident will
now see a `400`.
- [ ] Confirm returning `500` (instead of `404`) from `parseRequest`'s catch-all
is acceptable. This is the main externally-visible behavior change.
- [ ] Spot-check the `console.error` calls — if you'd rather route these through
a different logger (or silence them in certain environments), this is the place
to wire that up.
- [ ] Verify in a real Next.js app that `request.clone().text()` followed by
`JSON.parse` behaves identically to the old `request.clone().json()` path for
normal JSON payloads (jest tests cover the mock, but real `NextRequest`
behaviour is worth a smoke test).
### Notes
- No public API changes. No changeset added because the behaviour changes are
narrow bug fixes; happy to add one if you'd like this to ship as a patch
release entry.
- Pre-existing lint errors (`zsa-react` / `zsa-react-query` eslint parsing `dist/`
files) exist on `main` and are unrelated to this PR.
Link to Devin session: https://localhost:3000/sessions/c84471bd398546e19230962ebe50d560
Requested by: @IdoPesok
合并状态:未合并 3 条评论