From db759a9333f9152e6043f68d14b51c033733c07e Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Mon, 15 Jun 2026 00:57:30 -0400 Subject: [PATCH] schemagen: accept integral exponent/decimal numbers for integer slots JSON Schema's 'integer' type matches any number with a zero fractional part, but the generated builder rejected forms like 1e3 or 2.0 because it only ran std::from_chars. Keep that exact path for plain integer literals (so large values near INT64_MAX stay exact), and fall back to parsing as double for the rest, requiring an integral value within int64 range. Add for std::trunc and cover the new cases in test_gen. --- contrib/schemagen/test_gen.cpp | 26 +++++++++++++++++++++++ contrib/schemagen/weaseljson_schemagen.py | 18 ++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/contrib/schemagen/test_gen.cpp b/contrib/schemagen/test_gen.cpp index ac36de2..b507b93 100644 --- a/contrib/schemagen/test_gen.cpp +++ b/contrib/schemagen/test_gen.cpp @@ -134,6 +134,32 @@ int main() { printf("ok minimal object\n"); } + // ---- integer accepts any number with no fractional part (JSON Schema) ---- + { + struct { + const char *json; + int64_t age; + } cases[] = { + {R"({"name":"x","age":4e1})", 40}, + {R"({"name":"x","age":2.0})", 2}, + {R"({"name":"x","age":-1.5e1})", -15}, + {R"({"name":"x","age":0e0})", 0}, + }; + for (auto &c : cases) { + RootBuilder b; + WeaselJsonStatus s = parseStrided(b, c.json); + CHECK(s == WeaselJson_OK); + if (s == WeaselJson_OK) { + Root r = b.take(); + CHECK(r.age == c.age); + } + } + printf("ok integer in exponent/decimal form\n"); + } + // a fractional or out-of-range value is still rejected for an integer slot + expectReject(R"({"name":"x","age":2.5e0})", "non-integral exponent form"); + expectReject(R"({"name":"x","age":1e30})", "integer out of int64 range"); + // ---- schema-violation rejections ---- expectReject(R"({"name":"x"})", "missing required field 'age'"); expectReject(R"({"name":"x","age":"notnum"})", "wrong type (string for int)"); diff --git a/contrib/schemagen/weaseljson_schemagen.py b/contrib/schemagen/weaseljson_schemagen.py index 6f751df..e7a1a6c 100644 --- a/contrib/schemagen/weaseljson_schemagen.py +++ b/contrib/schemagen/weaseljson_schemagen.py @@ -487,6 +487,7 @@ class Emitter: #pragma once #include +#include #include #include #include @@ -928,8 +929,21 @@ private: if (si.cat == Cat::Int) {{ int64_t v = 0; auto r = std::from_chars(b, e, v); - if (r.ec != std::errc() || r.ptr != e) {{ reject(); return; }} - *(int64_t *)p = v; + if (r.ec == std::errc() && r.ptr == e) {{ + *(int64_t *)p = v; // plain integer literal: parsed exactly + }} else {{ + // JSON Schema "integer" accepts any number with no fractional part, + // including exponent/decimal forms like 1e3 or 2.0. Parse those as a + // double and require an integral value within int64 range. + double d = 0; + auto rd = std::from_chars(b, e, d); + if (rd.ec != std::errc() || rd.ptr != e || d != std::trunc(d) || + d < -9223372036854775808.0 || d >= 9223372036854775808.0) {{ + reject(); + return; + }} + *(int64_t *)p = (int64_t)d; + }} }} else {{ double v = 0; auto r = std::from_chars(b, e, v);