Summary
The fix for #1240 makes request.json() work — but only when its
return value is observed (passed to typeof, logged, accessed via
property). When the return is only used in an if (!body) truthiness
check (the standard Fastify body-parse pattern), Perry's DCE appears
to eliminate the call and body comes through as undefined.
Found while sweeping 27 call sites of const body = request.json() as any; if (!body) return reply.status(400)… across our routes after
pulling the #1240 fix.
Repro
Two handlers, structurally identical except the second one passes the
return through typeof. The first one returns 400 (body is undefined);
the second returns 200 (body is the parsed object).
// src/main.ts
import Fastify from "fastify";
const app = Fastify({ logger: false });
// Broken: return value only used for if(!body) truthiness.
app.post("/no-anchor", async (request, reply) => {
const body = (request as any).json() as any;
if (!body) return reply.status(400).send({ where: "no-anchor", body: "falsy" });
return { where: "no-anchor", body };
});
// Works: same logic but with a typeof "observation" first.
app.post("/with-anchor", async (request, reply) => {
const j = (request as any).json();
console.log("[with-anchor] typeof=" + typeof j); // <-- the anchor
const body = j as any;
if (!body) return reply.status(400).send({ where: "with-anchor", body: "falsy" });
return { where: "with-anchor", body };
});
// Works (this is the canonical Fastify pattern we ended up shipping):
app.post("/use-body", async (request, reply) => {
const body = (request as any).body as any;
if (!body) return reply.status(400).send({ where: "use-body", body: "falsy" });
return { where: "use-body", body };
});
await app.listen({ port: 3098, host: "127.0.0.1" });
Compile + test:
perry compile src/main.ts -o dcebug
./dcebug &
for ROUTE in no-anchor with-anchor use-body; do
echo "=== $ROUTE ==="
curl -s -X POST "http://127.0.0.1:3098/$ROUTE" \
-H 'Content-Type: application/json' -d '{"hello":"world"}'
echo
done
Actual output:
=== no-anchor ===
{"where":"no-anchor","body":"falsy"} ← 400, body undefined
=== with-anchor ===
{"where":"with-anchor","body":{"hello":"world"}} ← 200, body OK
=== use-body ===
{"where":"use-body","body":{"hello":"world"}} ← 200, body OK
The only difference between /no-anchor and /with-anchor is the
single console.log("typeof=" + typeof j) line. Removing it causes
request.json()'s return to be lost.
Expected
The standard Fastify body-parse pattern in user code is:
const body = request.json();
if (!body) return reply.status(400).send({ error: "Invalid body" });
const { email } = body;
This should consistently return the parsed body. Today the if (!body)
check by itself is apparently not "enough" of an observation to
prevent the codegen from eliminating the call.
Workaround we shipped
Switched all 27 call sites to (request as any).body (canonical
Fastify — .body is set by the content-type parser, which works
correctly under Perry already). This unblocked the full POST API
(login, signup, password reset, webhooks, IAP validation).
But the question for upstream: should the canonical
const body = request.json(); if (!body) … pattern work, or is
request.body the only blessed path?
Context
Found in the same investigation that produced #1240. After your fix
landed, I rebuilt locally (perry binary + perry-runtime staticlib,
both off ced5c862), confirmed the fix with a tiny smoke test that
returned typeof of the result (worked), then redeployed our api and
saw the original symptom return on the un-touched call sites. Took a
minute to realize that the smoke-test version "happened to" observe
the return via typeof and the real handlers didn't.
Compiler state at time of repro:
Summary
The fix for #1240 makes
request.json()work — but only when itsreturn value is observed (passed to
typeof, logged, accessed viaproperty). When the return is only used in an
if (!body)truthinesscheck (the standard Fastify body-parse pattern), Perry's DCE appears
to eliminate the call and
bodycomes through asundefined.Found while sweeping 27 call sites of
const body = request.json() as any; if (!body) return reply.status(400)…across our routes afterpulling the #1240 fix.
Repro
Two handlers, structurally identical except the second one passes the
return through
typeof. The first one returns 400 (body is undefined);the second returns 200 (body is the parsed object).
Compile + test:
Actual output:
The only difference between
/no-anchorand/with-anchoris thesingle
console.log("typeof=" + typeof j)line. Removing it causesrequest.json()'s return to be lost.Expected
The standard Fastify body-parse pattern in user code is:
This should consistently return the parsed body. Today the
if (!body)check by itself is apparently not "enough" of an observation to
prevent the codegen from eliminating the call.
Workaround we shipped
Switched all 27 call sites to
(request as any).body(canonicalFastify —
.bodyis set by the content-type parser, which workscorrectly under Perry already). This unblocked the full POST API
(login, signup, password reset, webhooks, IAP validation).
But the question for upstream: should the canonical
const body = request.json(); if (!body) …pattern work, or isrequest.bodythe only blessed path?Context
Found in the same investigation that produced #1240. After your fix
landed, I rebuilt locally (perry binary + perry-runtime staticlib,
both off
ced5c862), confirmed the fix with a tiny smoke test thatreturned
typeofof the result (worked), then redeployed our api andsaw the original symptom return on the un-touched call sites. Took a
minute to realize that the smoke-test version "happened to" observe
the return via
typeofand the real handlers didn't.Compiler state at time of repro:
ced5c862(today's main)