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<int64_t>. 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 <cmath> for std::trunc and cover the new cases in test_gen.
This commit is contained in:
2026-06-15 00:57:30 -04:00
parent 4301351042
commit db759a9333
2 changed files with 42 additions and 2 deletions
+26
View File
@@ -134,6 +134,32 @@ int main() {
printf("ok minimal object\n"); 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 ---- // ---- schema-violation rejections ----
expectReject(R"({"name":"x"})", "missing required field 'age'"); expectReject(R"({"name":"x"})", "missing required field 'age'");
expectReject(R"({"name":"x","age":"notnum"})", "wrong type (string for int)"); expectReject(R"({"name":"x","age":"notnum"})", "wrong type (string for int)");
+16 -2
View File
@@ -487,6 +487,7 @@ class Emitter:
#pragma once #pragma once
#include <charconv> #include <charconv>
#include <cmath>
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>
@@ -928,8 +929,21 @@ private:
if (si.cat == Cat::Int) {{ if (si.cat == Cat::Int) {{
int64_t v = 0; int64_t v = 0;
auto r = std::from_chars(b, e, v); auto r = std::from_chars(b, e, v);
if (r.ec != std::errc() || r.ptr != e) {{ reject(); return; }} if (r.ec == std::errc() && r.ptr == e) {{
*(int64_t *)p = v; *(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 {{ }} else {{
double v = 0; double v = 0;
auto r = std::from_chars(b, e, v); auto r = std::from_chars(b, e, v);