Files
weaselbot 86b58e83cb
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 57s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 50s
CI / pre-commit (pull_request) Successful in 52s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m41s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m29s
schemagen: reject absent additionalProperties
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
2026-07-19 22:06:23 -04:00

102 lines
4.1 KiB
Markdown

# weaseljson schemagen
Generates a C++ type and a streaming **builder** parser from a JSON Schema,
on top of weaseljson.
The builder copies incoming bytes straight into their final destinations in the
result struct as they arrive (no intermediate DOM). When parsing completes you
`take()` ownership of the result.
```sh
python3 weaseljson_schemagen.py schema.json -o parsed.h --namespace myschema
```
## Usage
```cpp
#include "parsed.h"
using namespace myschema;
RootBuilder b;
WeaselJsonStatus s = b.feed(buf, len); // call repeatedly with chunks; buf may
// be modified in place (unescaping)
if (s == WeaselJson_AGAIN) s = b.finish();
if (s == WeaselJson_OK) {
Root value = b.take(); // ownership of the parsed result
}
```
`feed`/`finish` return the usual `WeaselJsonStatus`; `WeaselJson_OK` means the
document is both valid JSON and schema-valid, and `WeaselJson_REJECT` covers
both malformed JSON and schema violations. The builder holds interior pointers
into the result, so it is non-movable.
## Schema -> C++ mapping
| JSON Schema | C++ |
|-----------------------------------------------|----------------------------------------|
| `object` with `properties` | `struct` |
| required property | value member |
| non-required property | `std::optional<T>` |
| `["T", "null"]` (nullable) | `std::optional<T>` (accepts `null`) |
| `string` / `integer` / `number` / `boolean` | `std::string` / `int64_t` / `double` / `bool` |
| `enum` of strings | `enum class : int` |
| `array` with `items` | `std::vector<T>` |
| `$ref` to `$defs`/`definitions` | the referenced named struct |
| recursive `$ref` | `std::unique_ptr<T>` (cycle broken) |
| `additionalProperties: false` | unknown keys rejected |
| `additionalProperties` absent / `true` | not supported (rejected at generation) |
## Schema violations (rejected at parse time)
- missing required property (top-level or nested)
- wrong JSON type for a property / array element
- `null` for a non-nullable slot
- value not in a string `enum`
- a number not representable in the target type (e.g. `1.5` for an `integer`)
- duplicate object keys
- unknown key in any object
## Not supported (rejected at generation time, no fallback)
`oneOf` / `anyOf` / `allOf` / `not` / `if`-`then`-`else`,
`patternProperties`, `additionalProperties` absent or set to `true`,
`additionalProperties` with a schema (typed map), `prefixItems` (tuples),
`const`,
`dependentSchemas`/`dependentRequired`, union `type` lists other than
`["T", "null"]`, non-string enums, and remote (`$ref` to other documents).
## Notes
- Strings (the bulk payload) are appended directly into their destination
`std::string`, even when split across `feed` calls. Numbers are accumulated in
a small scratch buffer and converted on completion, since an `int64_t`/`double`
cannot hold partial digits.
- `test_gen.cpp` generates from `example.schema.json` and exercises the parser
byte-by-byte (covering chunked strings/numbers), plus the rejection cases.
## Testing
The schemagen tests are registered with CTest and run as part of the default
`ctest` invocation from the build directory:
```sh
cmake -S . -B build
make -C build -j "$(nproc)"
cd build
ctest --output-on-failure
```
For local development you can also use the convenience script:
```sh
cd contrib/schemagen
./run_tests.sh
```
Both regenerate the example parser (`gen.h`) and a regression parser with 40
required properties (`big.h`), compile `test_gen.cpp` and `test_big.cpp`, and run
them. `test_big.cpp` specifically covers issue #3: it checks that a 40-property
object accepts all fields, rejects a missing field at index 32, and rejects
duplicate keys around the 32-bit boundary.