ITADN

Surface silently-swallowed errors in zsa and zsa-openapi

#254Pull Requeststaging-devin-ai-integration[bot] 创建于 2026-04-20
## 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 条评论