fix(core): reject unrepresentable JSON numbers instead of storing null (#455)
AnyCodable.init(from:) ended with an unconditional `value = nil` fallback, so
any JSON value that failed every decode attempt became nil and RawJSON stored
it re-encoded as the JSON literal `null`. The reachable case is a number outside
Double's range: JSONDecoder throws on a non-conforming float, `try?` swallowed
it, and the value became null.
So `{"maximum": 1e999}` silently became `{"maximum": null}`. The schema apfel
actually applied was not the schema the caller sent, the response could violate
a constraint the caller believed was in force, and nothing in the request or the
response indicated the substitution. The null is also structurally wrong where
the schema keyword requires a number, so downstream SchemaConverter behaviour
depended on a value the caller never wrote. RawJSON backs both
response_format.json_schema.schema and tools[].function.parameters.
Every JSON value is null, bool, int, double, string, object or array, and null
is handled by decodeNil() before the fallback, so reaching the end means the
input genuinely is not representable. It now throws a DecodingError, which the
existing handler catch turns into a 400.
Verified against the live server: the 1e999 schema returns 400, a normal schema
with an enum still returns 200 with finish_reason tool_calls, and the server
stays up. A test pins that an explicit `null` still decodes -- the fix has to
reject only what cannot be represented.
Candidate PRs #459, #460 and #461 all proposed this same one-line change and
could not be applied directly because #462 restructured the same function;
taken by hand with #459's wording, tests written against the merged shape.
1075 unit tests pass.
Closes #455
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ccLBbEaVVd4sJyUd5wyhA A
Arthur Ficial committed
fa0448de4a0085da41c10a9cd0bf7b597f66a3b0
Parent: 2ed07f6