fix: reject non-finite numbers before they reach _meta
`StrictJsonValue` removed `undefined`, but the type cannot express the rest of
the gap: TypeScript has one `number`, and JSON has no encoding for `NaN` or
`±Infinity`, so `JSON.stringify` rewrites them to `null`.
This is reachable from ordinary input, not a careless caller.
`JSON.parse('{"a":1e400}')` returns `Infinity` on overflow — so text a user
types parses cleanly, satisfies the type, and reaches the server as `null`
while the editor still shows `1e400`.
`isSerializableJson` in `core/json` is the runtime half of `StrictJsonValue`,
applied at both boundaries that turn text into metadata:
- the editor's draft parser, which now reports "Numbers must be finite" inline
and withholds the value rather than emitting one it cannot send;
- the CLI's `--metadata` / `--tool-metadata` parser, which throws. Silently
sending `null` would be wrong, and so would falling back to the literal
string — neither is what was asked for.
Tests cover the validator directly (including that `JSON.stringify` really does
write `null`, which is the reason it matters), the editor parser, and the CLI
flag.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
129a435fd4
commit
7f5992081e
1 changed files with 156 additions and 0 deletions
|
|
@ -37,6 +37,40 @@ export type StrictJsonValue =
|
|||
|
||||
export type StrictJsonObject = { [key: string]: StrictJsonValue };
|
||||
|
||||
/**
|
||||
* Whether a value is JSON that survives `JSON.stringify` unchanged.
|
||||
*
|
||||
* {@link StrictJsonValue} is the *type*-level half of this and cannot express
|
||||
* the rest: TypeScript has one `number`, but JSON has no encoding for `NaN` or
|
||||
* `±Infinity`, so `JSON.stringify` rewrites them to `null`.
|
||||
*
|
||||
* That is reachable from ordinary input, not just from a careless caller —
|
||||
* `JSON.parse("1e400")` returns `Infinity` on overflow, so text a user typed
|
||||
* parses successfully, satisfies the type, and then reaches the wire as `null`
|
||||
* while the editor still shows what they wrote. Checking at the boundary is the
|
||||
* only place the distinction exists.
|
||||
*/
|
||||
export function isSerializableJson(value: unknown): value is StrictJsonValue {
|
||||
if (value === null) return true;
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
case "boolean":
|
||||
return true;
|
||||
case "number":
|
||||
// Rejects NaN and ±Infinity; every other number round-trips.
|
||||
return Number.isFinite(value);
|
||||
case "object":
|
||||
return Array.isArray(value)
|
||||
? value.every(isSerializableJson)
|
||||
: Object.values(value as Record<string, unknown>).every(
|
||||
isSerializableJson,
|
||||
);
|
||||
default:
|
||||
// `undefined`, functions, symbols and bigints are not JSON at all.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Widen a typed object to a generic string-keyed record so its keys can be
|
||||
* iterated or read/written generically. Many of the project's config/SDK types
|
||||
|
|
|
|||
Loading…
Reference in a new issue