contrib/schemagen/weaseljson_schemagen.py screens a schema node against a hard-coded
list of unsupported keywords (Builder.build_type, lines 314-331). Anything that is not
on that list is silently ignored. That includes validation keywords that decide whether
a document is schema-valid, e.g. minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, minLength, maxLength, pattern, format, minItems, maxItems, uniqueItems, contains, minContains, minProperties, maxProperties, propertyNames, unevaluatedProperties.
Generation succeeds with exit code 0 and no diagnostic, the emitted header contains no
trace of the constraint, and the generated RootBuilder therefore returns WeaselJson_OK for documents that violate the schema.
This contradicts what the tool promises:
contrib/schemagen/weaseljson_schemagen.py:8-9: "Only the subset of JSON Schema that maps naturally onto C++ structs is supported.
Anything else is rejected at generation time (no JsonValue fallback)."
contrib/schemagen/README.md:29-31: "WeaselJson_OK means the document is both valid JSON and schema-valid"
contrib/schemagen/README.md:60-68 ("Not supported (rejected at generation time, no
fallback)") lists const -- a pure validation keyword with no effect on the C++ mapping.
So the intended design is clearly to reject unsupported validation keywords rather
than silently drop them.
This is the same class of defect as #55 / #63 (README promises generation-time rejection,
code silently changes what the parser accepts), but it is not a duplicate of any listed
issue: #55/#63 were about additionalProperties and required, not about keywords that
are dropped entirely.
Expected: either generation fails for these schemas (GenError, like const does), or the
builder rejects the two invalid documents.
For contrast, the same schema with a const sibling on age is rejected at generation
time, which shows the reject-list is the intended mechanism:
$ python3 contrib/schemagen/weaseljson_schemagen.py min_const.schema.json -o min_const.h
error: 'const' is not supported (no natural C++ struct mapping)
$ echo$?1
Every keyword in the list below was checked with weaseljson_schemagen.py, and all of them exit 0 silently and leave no trace in the
generated header:
forbadin("allOf","oneOf","anyOf","not","if","then","else","patternProperties","prefixItems","const","dependentSchemas","dependentRequired",):ifbadinnode:raiseGenError(f"'{bad}' is not supported (no natural C++ struct mapping)")
There is no allow-list / unknown-keyword check. Any key that is not consulted later in build_type (type, enum, properties, required, items, $ref, additionalProperties) is dropped on the floor, because the function only reads the
keys it knows about. Note that if/then/else and const are rejected even though
they have no C++-mapping implication at all -- they are rejected precisely because the
generated parser cannot enforce them -- which is exactly the argument that applies to minimum/pattern/uniqueItems/... too.
_build_object (:412-462) then emits a field for each property and never records the
constraints, so the generated cbNumberData/cbStringData/cbEndArray code has no way
to know about them.
Impact
Silent schema-validation bypass. A user who generates a parser from a schema and treats WeaselJson_OK as "the document is valid input" (which is what README.md:29-31
promises) will accept out-of-range numbers, strings that violate pattern/minLength,
arrays with wrong sizes or duplicate elements, objects with too many properties, and so
on, and will happily store and act on that data. Because nothing is printed at generation
time, there is no way for the user to notice the constraint was dropped short of diffing
the generated header against the schema.
Suggested fix
Invert the check in build_type: define the set of keywords the generator actually
understands (plus harmless annotations that are safe to ignore, e.g. title, description, $comment, default, examples, deprecated, readOnly, writeOnly, $id, $schema, $anchor, definitions/$defs) and raise GenError for any other key
found in a schema node. At minimum, reject the validation keywords listed above, each of
which is a document-validity constraint that the generated parser cannot enforce. A
regression test can live next to the existing SchemagenAdditionalPropertiesTest / SchemagenRequiredPropertyTest classes in contrib/schemagen/test_schemagen.py.
## Summary
`contrib/schemagen/weaseljson_schemagen.py` screens a schema node against a hard-coded
list of unsupported keywords (`Builder.build_type`, lines 314-331). Anything that is not
on that list is silently ignored. That includes **validation keywords that decide whether
a document is schema-valid**, e.g. `minimum`, `maximum`, `exclusiveMinimum`,
`exclusiveMaximum`, `multipleOf`, `minLength`, `maxLength`, `pattern`, `format`,
`minItems`, `maxItems`, `uniqueItems`, `contains`, `minContains`, `minProperties`,
`maxProperties`, `propertyNames`, `unevaluatedProperties`.
Generation succeeds with exit code 0 and no diagnostic, the emitted header contains no
trace of the constraint, and the generated `RootBuilder` therefore returns
`WeaselJson_OK` for documents that violate the schema.
This contradicts what the tool promises:
* `contrib/schemagen/weaseljson_schemagen.py:8-9`:
*"Only the subset of JSON Schema that maps naturally onto C++ structs is supported.
Anything else is rejected at generation time (no JsonValue fallback)."*
* `contrib/schemagen/README.md:29-31`:
*"`WeaselJson_OK` means the document is both valid JSON and schema-valid"*
* `contrib/schemagen/README.md:60-68` ("Not supported (rejected at generation time, no
fallback)") lists `const` -- a pure validation keyword with no effect on the C++ mapping.
So the intended design is clearly to *reject* unsupported validation keywords rather
than silently drop them.
This is the same class of defect as #55 / #63 (README promises generation-time rejection,
code silently changes what the parser accepts), but it is not a duplicate of any listed
issue: #55/#63 were about `additionalProperties` and `required`, not about keywords that
are dropped entirely.
## Reproduction
`min.schema.json`:
```json
{
"type": "object",
"additionalProperties": false,
"required": ["age", "name", "tags"],
"properties": {
"age": {"type": "integer", "minimum": 18, "maximum": 120, "multipleOf": 2},
"name": {"type": "string", "minLength": 3, "pattern": "^[a-z]+$"},
"tags": {"type": "array", "items": {"type": "string"}, "minItems": 2, "uniqueItems": true}
}
}
```
Generate (succeeds silently, no warning, no trace of the constraints in the output):
```sh
$ python3 contrib/schemagen/weaseljson_schemagen.py min.schema.json -o min.h --namespace minschema
$ echo $?
0
$ grep -c -E 'minimum|maximum|multipleOf|minLength|pattern|minItems|uniqueItems' min.h
0
```
The generated builder then reports success for documents that violate every one of those
constraints:
```cpp
#include "min.h"
static WeaselJsonStatus feedAll(minschema::RootBuilder &b, std::string doc) {
std::string copy = doc; // feed() unescapes in place
WeaselJsonStatus s = b.feed(copy.data(), (int)copy.size());
if (s == WeaselJson_AGAIN) s = b.finish();
return s;
}
int main() {
const char *docs[] = {
R"({"age":18,"name":"alice","tags":["a","b"]})", // schema-valid
R"({"age":1,"name":"A","tags":["x","x"]})", // minimum/minLength/pattern/minItems/uniqueItems violated
R"({"age":1000,"name":"ALICE","tags":[]})", // maximum/multipleOf/pattern/minItems violated
};
for (const char *d : docs) {
minschema::RootBuilder b;
WeaselJsonStatus s = feedAll(b, d);
printf("status=%s %s\n", s == WeaselJson_OK ? "OK" : (s == WeaselJson_REJECT ? "REJECT" : "OTHER"), d);
if (s == WeaselJson_OK) {
auto v = b.take();
printf(" parsed: age=%lld name=\"%s\" tags=%zu\n", (long long)v.age, v.name.c_str(), v.tags.size());
}
}
}
```
Observed output (built with `g++ -std=c++20 -I include -I . min_main.cpp src/lib.cpp`):
```
status=OK {"age":18,"name":"alice","tags":["a","b"]}
parsed: age=18 name="alice" tags=2
status=OK {"age":1,"name":"A","tags":["x","x"]}
parsed: age=1 name="A" tags=2
status=OK {"age":1000,"name":"ALICE","tags":[]}
parsed: age=1000 name="ALICE" tags=0
```
Expected: either generation fails for these schemas (`GenError`, like `const` does), or the
builder rejects the two invalid documents.
For contrast, the same schema with a `const` sibling on `age` is rejected at generation
time, which shows the reject-list is the intended mechanism:
```sh
$ python3 contrib/schemagen/weaseljson_schemagen.py min_const.schema.json -o min_const.h
error: 'const' is not supported (no natural C++ struct mapping)
$ echo $?
1
```
Every keyword in the list below was checked with
`weaseljson_schemagen.py`, and all of them exit 0 silently and leave no trace in the
generated header:
| keyword | node type used | generator exit |
|---|---|---|
| `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` | integer | 0 |
| `minLength`, `maxLength`, `pattern`, `format` | string | 0 |
| `minItems`, `maxItems`, `uniqueItems`, `contains`, `minContains`, `additionalItems` | array | 0 |
| `minProperties`, `maxProperties`, `propertyNames`, `unevaluatedProperties` | object | 0 |
## Root cause
`Builder.build_type` (`contrib/schemagen/weaseljson_schemagen.py:314-331`) rejects only:
```python
for bad in (
"allOf", "oneOf", "anyOf", "not", "if", "then", "else",
"patternProperties", "prefixItems", "const",
"dependentSchemas", "dependentRequired",
):
if bad in node:
raise GenError(f"'{bad}' is not supported (no natural C++ struct mapping)")
```
There is no allow-list / unknown-keyword check. Any key that is not consulted later in
`build_type` (`type`, `enum`, `properties`, `required`, `items`, `$ref`,
`additionalProperties`) is dropped on the floor, because the function only *reads* the
keys it knows about. Note that `if`/`then`/`else` and `const` are rejected even though
they have no C++-mapping implication at all -- they are rejected precisely because the
generated parser cannot enforce them -- which is exactly the argument that applies to
`minimum`/`pattern`/`uniqueItems`/... too.
`_build_object` (`:412-462`) then emits a field for each property and never records the
constraints, so the generated `cbNumberData`/`cbStringData`/`cbEndArray` code has no way
to know about them.
## Impact
Silent schema-validation bypass. A user who generates a parser from a schema and treats
`WeaselJson_OK` as "the document is valid input" (which is what `README.md:29-31`
promises) will accept out-of-range numbers, strings that violate `pattern`/`minLength`,
arrays with wrong sizes or duplicate elements, objects with too many properties, and so
on, and will happily store and act on that data. Because nothing is printed at generation
time, there is no way for the user to notice the constraint was dropped short of diffing
the generated header against the schema.
## Suggested fix
Invert the check in `build_type`: define the set of keywords the generator actually
understands (plus harmless annotations that are safe to ignore, e.g. `title`,
`description`, `$comment`, `default`, `examples`, `deprecated`, `readOnly`, `writeOnly`,
`$id`, `$schema`, `$anchor`, `definitions`/`$defs`) and raise `GenError` for any other key
found in a schema node. At minimum, reject the validation keywords listed above, each of
which is a document-validity constraint that the generated parser cannot enforce. A
regression test can live next to the existing
`SchemagenAdditionalPropertiesTest` / `SchemagenRequiredPropertyTest` classes in
`contrib/schemagen/test_schemagen.py`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
contrib/schemagen/weaseljson_schemagen.pyscreens a schema node against a hard-codedlist of unsupported keywords (
Builder.build_type, lines 314-331). Anything that is noton that list is silently ignored. That includes validation keywords that decide whether
a document is schema-valid, e.g.
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOf,minLength,maxLength,pattern,format,minItems,maxItems,uniqueItems,contains,minContains,minProperties,maxProperties,propertyNames,unevaluatedProperties.Generation succeeds with exit code 0 and no diagnostic, the emitted header contains no
trace of the constraint, and the generated
RootBuildertherefore returnsWeaselJson_OKfor documents that violate the schema.This contradicts what the tool promises:
contrib/schemagen/weaseljson_schemagen.py:8-9:"Only the subset of JSON Schema that maps naturally onto C++ structs is supported.
Anything else is rejected at generation time (no JsonValue fallback)."
contrib/schemagen/README.md:29-31:"
WeaselJson_OKmeans the document is both valid JSON and schema-valid"contrib/schemagen/README.md:60-68("Not supported (rejected at generation time, nofallback)") lists
const-- a pure validation keyword with no effect on the C++ mapping.So the intended design is clearly to reject unsupported validation keywords rather
than silently drop them.
This is the same class of defect as #55 / #63 (README promises generation-time rejection,
code silently changes what the parser accepts), but it is not a duplicate of any listed
issue: #55/#63 were about
additionalPropertiesandrequired, not about keywords thatare dropped entirely.
Reproduction
min.schema.json:Generate (succeeds silently, no warning, no trace of the constraints in the output):
The generated builder then reports success for documents that violate every one of those
constraints:
Observed output (built with
g++ -std=c++20 -I include -I . min_main.cpp src/lib.cpp):Expected: either generation fails for these schemas (
GenError, likeconstdoes), or thebuilder rejects the two invalid documents.
For contrast, the same schema with a
constsibling onageis rejected at generationtime, which shows the reject-list is the intended mechanism:
Every keyword in the list below was checked with
weaseljson_schemagen.py, and all of them exit 0 silently and leave no trace in thegenerated header:
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOfminLength,maxLength,pattern,formatminItems,maxItems,uniqueItems,contains,minContains,additionalItemsminProperties,maxProperties,propertyNames,unevaluatedPropertiesRoot cause
Builder.build_type(contrib/schemagen/weaseljson_schemagen.py:314-331) rejects only:There is no allow-list / unknown-keyword check. Any key that is not consulted later in
build_type(type,enum,properties,required,items,$ref,additionalProperties) is dropped on the floor, because the function only reads thekeys it knows about. Note that
if/then/elseandconstare rejected even thoughthey have no C++-mapping implication at all -- they are rejected precisely because the
generated parser cannot enforce them -- which is exactly the argument that applies to
minimum/pattern/uniqueItems/... too._build_object(:412-462) then emits a field for each property and never records theconstraints, so the generated
cbNumberData/cbStringData/cbEndArraycode has no wayto know about them.
Impact
Silent schema-validation bypass. A user who generates a parser from a schema and treats
WeaselJson_OKas "the document is valid input" (which is whatREADME.md:29-31promises) will accept out-of-range numbers, strings that violate
pattern/minLength,arrays with wrong sizes or duplicate elements, objects with too many properties, and so
on, and will happily store and act on that data. Because nothing is printed at generation
time, there is no way for the user to notice the constraint was dropped short of diffing
the generated header against the schema.
Suggested fix
Invert the check in
build_type: define the set of keywords the generator actuallyunderstands (plus harmless annotations that are safe to ignore, e.g.
title,description,$comment,default,examples,deprecated,readOnly,writeOnly,$id,$schema,$anchor,definitions/$defs) and raiseGenErrorfor any other keyfound in a schema node. At minimum, reject the validation keywords listed above, each of
which is a document-validity constraint that the generated parser cannot enforce. A
regression test can live next to the existing
SchemagenAdditionalPropertiesTest/SchemagenRequiredPropertyTestclasses incontrib/schemagen/test_schemagen.py.