Per the README contract, an object schema without an explicit
`additionalProperties` was documented as rejected at generation time,
but the code defaulted it to `false` and silently generated a strict
parser that rejects valid documents with extra properties.
Make the code match the documented contract by raising GenError when
`additionalProperties` is absent, with a clear message instructing the
author to set it explicitly to false. Update the README prose section
to list absent additionalProperties alongside true, and fix existing
schemas and tests to declare additionalProperties explicitly.
Closes#55
Move the zero-detection check in the generated parseJsonInt64 before the
finalExp < 0 guard. Previously, valid JSON numbers whose mathematical
value is 0 but written with a large negative exponent (e.g. 0e-2,
0.0e-2, -0e-2, 0e-20) were rejected because the negative-finalExp early
return ran before the all-zero-digits branch could set out = 0. Non-zero
values with negative exponents are still correctly rejected.
Closes#56
The generator already preserves nullability for self-referential object
$defs thanks to prior fixes, but issue #14 had no regression coverage.
Add a test using the exact reproduction schema from the issue and verify
that both the outer and recursive `self` fields accept `null`.
Cyclic array definitions (directly or through a chain of array $defs)
created a self-referential TArr, which then caused infinite recursion in
base_cpp, storage_cpp, and _walk_arrays.
Object-only cycles are already broken with std::unique_ptr, but
array-only cycles have no object edge for break_cycles to cut.
Detect them after building an array's items by following TArr.elem links
and raise a clear GenError so generation fails gracefully rather than
overflowing the Python stack.
Closes#33
Fixes#35.
Add a helper to escape C++ string literals so that JSON control characters
(\n, \r, \t, and other bytes below 0x20) are emitted as escape sequences
instead of raw bytes. Use it for:
- field comments that include the JSON property key
- object key comparison literals in matchKey()
- enum name arrays
Also add regression tests that generate and syntax-check headers for
schemas containing newlines and other control characters in property keys
and enum values.
Replace the O(k^2) loop that erased leading zeros one byte at a time
from the front of a std::string with a single linear scan and one
erase(0, n) call.
Also adds regression tests for issue #34:
- correctness cases for numbers with leading fractional zeros
- a static check that the generated code no longer contains the
quadratic pattern
- a large-input case (100k leading zeros) that reproduces the
vulnerable shape
Closes#34
If WeaselJsonParser_create returns nullptr (e.g. negative stack size or allocation failure), set the existing error_ flag so that subsequent feed()/finish() calls return WeaselJson_REJECT instead of dereferencing the null parser_.
Also add a regression test in test_gen.cpp that constructs a RootBuilder with an invalid stack size and verifies it rejects without crashing.
Closes#36
The generated RootBuilder crashed (undefined behavior on std::vector::back())
when a JSON document's root value was a scalar or null while the schema
declared a non-nullable object or array root. The stack starts empty for
object/array roots, but cbStringData, cbNumberData, and cbBool called
stack_.back() without checking for an empty stack.
Add an empty-stack guard to the three scalar callbacks so they reject
instead of crashing. cbNull already handles the empty-stack case.
Regression tests added for:
- non-nullable object root rejecting null, boolean, number, and string roots
- nullable object root rejecting scalar roots
- nullable array root rejecting scalar roots
Closes#16
The previous change to cache object definitions started returning the
bare TObj from build_type for object schemas, discarding the nullable
flag. This caused nullable root objects to be emitted as plain structs
instead of std::optional<RootInner>, breaking the nullable root tests.
Return the (TObj, nullable) tuple so callers (including the root
emitter) see the correct nullability again.
Extend the existing per-definition cache (`self._building`) to enum,
array, and scalar $defs, not just object definitions. This ensures
that multiple $refs to the same non-object definition reuse the same
C++ type instead of generating Role, Role2, Role3, etc.
- Cache the built (type, nullable) tuple under defname for enum,
scalar, and array definitions.
- Pre-register array definitions before recursing into items so $ref
cycles resolve to the same TArr instance.
- Store object definitions as (TObj, nullable) tuples so nullable object
$defs also preserve their nullability when referenced.
Add a regression test for issue #17 covering reused enum and array-of-enum
$defs.
Replace the double-based fallback in generated integer slots with a
string-to-int64 parser that handles decimal points and exponents
without losing precision near the int64 boundaries.
The old path used std::from_chars<double> and compared against
±9223372036854775808.0, which rounds the int64 max and min so that
valid values are rejected and out-of-range negatives are accepted.
The new helper:
- Parses sign, integer part, optional fraction, and optional exponent.
- Strips trailing zeros to cancel fractional places.
- Rejects non-integral values and overflow using exact uint64_t
arithmetic.
Adds regression tests covering root integer and object-field integer
boundary values, including the cases from issue #19.
Following review feedback, the generator no longer supports permissive
objects. Changes:
- Reject `additionalProperties: true` at generation time.
- Treat an absent `additionalProperties` as `false`, so every object is
strict by default and unknown keys are rejected during parsing.
- Remove the now-dead permissive-object infrastructure: `Kind::Skip`,
`Cat::Skip`, `kSkip`, the per-frame `unknown` key set, and `isStrict()`.
- Update the README feature/rejection tables accordingly.
- Remove the permissive "loose" object from example.schema.json and the
associated tests from test_gen.cpp.
- Add Python unit tests verifying the new `additionalProperties` behavior.
All tests pass (`ctest --output-on-failure`).
Track unknown keys in a per-object unordered_set so that permissive
objects (additionalProperties absent/true) still reject duplicate keys,
matching the README guarantee.
- Add std::unordered_set<std::string> to Frame.
- Insert unknown keys in cbKeyData and reject duplicates before skipping.
- Add a permissive "loose" subobject to example.schema.json.
- Test single unknown key accepted and duplicate unknown/known keys rejected.
The C++ code generator now handles schemas where the top-level type is
nullable ("type": ["object", "null"], ["string", "null"], or
["array", "null"]).
Changes to weaseljson_schemagen.py:
- Rename the inner object struct when the root is a nullable object, so
the `using Root = std::optional<...>` alias no longer conflicts with
`struct Root`.
- Treat nullable root objects and arrays as container roots, emplacing
the inner value before pushing the root frame and pointing the frame at
the contained value.
- For nullable root scalars/enums, engage() now returns a pointer to the
value inside the optional rather than to the optional wrapper itself.
- cbNull() now safely accepts a top-level null when the root is nullable
and rejects it otherwise.
Regression tests added:
- nullable_object.schema.json + test_nullable_root.cpp
- nullable_string.schema.json
- nullable_array.schema.json
Closes#13
Make the type-name allocator aware of the identifiers the generator emits
itself (`Root` alias, `Skip`/`RootScalar` Kind enumerators) so user `$defs`
names can no longer collide with them. Array-kind names (`Arr0`, `Arr1`, ...)
are now allocated only after checking for object/enum names, preventing
duplicate `Kind` enumerators when a schema defines e.g. `Arr0`.
Add Python regression tests that also syntax-check the generated headers
with a C++ compiler.
Closes#21
Add concept, consteval, constinit, co_await, co_return, co_yield,
requires, module, and import to _CPP_KEYWORDS so property names that
happen to be C++20 keywords get sanitized with a trailing underscore.
Closes#18
Remove the standalone "Run schemagen tests" workflow step and instead
add the big-schema regression test to contrib/schemagen/CMakeLists.txt
so ctest picks it up alongside schemagen_example. Update README to
document both `ctest` and the convenience `./run_tests.sh`.
Closes#3
Replace the 32-bit `uint32_t seen` bitmask with a `std::vector<uint64_t>`
bitset sized to the actual field count. `requiredMask` is now a vector of
the same word count, and required-field checks use per-word masking so that
optional fields do not cause false rejections.
Adds big.schema.json + test_big.cpp regression tests covering the issue
reproducer (40 required properties, missing/duplicate at index 32), plus
run_tests.sh to exercise both test_gen.cpp and test_big.cpp.
Fixes#3
Addresses review feedback: keep the schemagen-specific CMake rules
close to the tool instead of inline in the top-level CMakeLists.txt.
The subdirectory file is added from the root and guarded by the same
Python3 availability check that was already in use.
Distinct JSON enum values can sanitize to the same C++ identifier
(e.g. "foo-bar" and "foo_bar" both become `foo_bar`), producing an
invalid `enum class` with duplicate constants.
Add `unique_enum_identifiers()` which appends a numeric suffix to
later collisions while preserving enum declaration order, so the
index-to-JSON-value mapping used by the generated parser stays intact.
Also add contrib/schemagen/test_schemagen.py and wire the schemagen
Python tests plus the existing example.schema.json/test_gen.cpp example
into ctest via CMakeLists.txt.
Closes#4
For array types whose items are nullable ({"type": ["T", "null"]}),
the generated builder previously called valueComplete() on cbNull() without
appending anything to the owning vector. Null entries were silently dropped,
so vector indices no longer matched JSON array indices.
Generate isArrayKind() / appendNull() helpers and have cbNull() append a
default-constructed element when the current frame is an array. For
std::optional<T> items this appends an empty optional; for std::unique_ptr<T>
items it appends a null pointer. Add nullable string/integer array fields to
the example schema and test coverage to verify indices are preserved.
Fixes#5
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.
Generates a C++ type and a streaming builder parser from a JSON Schema,
built on weaseljson. The builder copies incoming bytes directly into their
final destinations in the result struct (no intermediate DOM) and hands
back ownership via take() once parsing completes.
Supports objects/structs, required vs optional (std::optional) fields,
nullable types, string enums, arrays, $ref including recursion (broken
with unique_ptr), and additionalProperties (strict reject or skip).
Unsupported schema constructs are rejected at generation time; schema
violations are rejected at parse time.