Skip to content

Fetch Response: honor Response.json() init options #2638

Description

@andrewtdiz

Summary

Perry implements Response.json(data), but the current static factory ignores the optional init argument and uses a non-Node default statusText.

Node applies init.status, init.statusText, and init.headers, validates invalid status/statusText values, preserves a caller-provided content-type, and defaults statusText to the empty string. Perry's origin/main lowering passes only the JSON value to the runtime helper, and the helper always creates a 200 OK response with only content-type: application/json.

Expected Node behavior

Local Node v25.9.0 probe:

const base = Response.json({ a: 1 });
console.log(base.status, JSON.stringify(base.statusText), base.headers.get("content-type"), await base.text());

const r = Response.json(
  { ok: true },
  {
    status: 201,
    statusText: "Created-ish",
    headers: { "x-test": "yes", "content-type": "application/custom" },
  },
);
console.log(r.status, JSON.stringify(r.statusText), r.ok);
console.log(r.headers.get("content-type"), r.headers.get("x-test"));
console.log(await r.text());

for (const init of [{ status: 99 }, { status: 600 }, { statusText: "bad\nline" }]) {
  try {
    Response.json({ a: 1 }, init);
  } catch (e) {
    console.log(JSON.stringify(init), e.name, e.message.split("\n")[0]);
  }
}

Output:

200 "" application/json {"a":1}
201 "Created-ish" true
application/custom yes
{"ok":true}
{"status":99} RangeError init["status"] must be in the range of 200 to 599, inclusive.
{"status":600} RangeError init["status"] must be in the range of 200 to 599, inclusive.
{"statusText":"bad\nline"} TypeError Invalid statusText

Current Perry evidence

From origin/main:

  • docs/runtime-parity.md marks Response.json(data, init?) covered, while docs/runtime-parity-gaps.md still lists it as a Web-global gap.
  • crates/perry-hir/src/lower/expr_call/module_static.rs lowers Response.json into a static Fetch factory call but does not distinguish the init semantics.
  • crates/perry-codegen/src/lower_call/options/fetch.rs handles static_json by lowering only args[0] and calling js_response_static_json(value) with a single argument.
  • crates/perry-ext-fetch/src/lib.rs::js_response_static_json() always stores status: 200, status_text: "OK", and a fresh content-type: application/json header map.
  • test-files/test_gap_fetch_response.ts checks only body JSON and content-type for the one-argument form, so it misses init handling and the default statusText mismatch.

Suggested test surface

Add parity fixtures that verify:

  • Response.json(data).statusText === "" and default content-type is application/json.
  • Response.json(data, { status, statusText, headers }) applies status, status text, and custom headers.
  • caller-provided content-type is preserved rather than overwritten.
  • invalid status values outside 200..599 throw RangeError.
  • invalid statusText characters throw TypeError.

Duplicate search notes

Before filing I searched issues and PRs for Response.json init status headers, Response.json content-type statusText, and Response.json init. I found no existing issue or PR covering these Response.json() init semantics. This is separate from #2608's globalThis.Response binding issue, #2634's missing Response.error(), and #2637's Response.redirect() validation/statusText behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions