CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 51s
CI / pre-commit (pull_request) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m34s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m27s
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
209 lines
7.6 KiB
C++
209 lines
7.6 KiB
C++
// Test harness for the generated example.schema.json parser.
|
|
// Feeds input one byte at a time to exercise chunked string/number paths.
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
#include "gen.h"
|
|
|
|
using namespace weasel_schema;
|
|
|
|
// Feed `in` one byte at a time. Returns final status.
|
|
static WeaselJsonStatus parseStrided(RootBuilder &b, std::string in) {
|
|
for (size_t i = 0; i < in.size(); ++i) {
|
|
char c = in[i];
|
|
WeaselJsonStatus s = b.feed(&c, 1);
|
|
if (s != WeaselJson_AGAIN)
|
|
return s;
|
|
}
|
|
return b.finish();
|
|
}
|
|
|
|
static int failures = 0;
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
|
++failures; \
|
|
} \
|
|
} while (0)
|
|
|
|
static void expectReject(const std::string &json, const char *what) {
|
|
RootBuilder b;
|
|
WeaselJsonStatus s = parseStrided(b, json);
|
|
if (s == WeaselJson_REJECT) {
|
|
printf("ok reject: %s\n", what);
|
|
} else {
|
|
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
|
|
json.c_str());
|
|
++failures;
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
// ---- happy path, full feature coverage, byte-strided ----
|
|
{
|
|
std::string json = R"({
|
|
"name": "Ada É",
|
|
"age": 36,
|
|
"height": 1.75,
|
|
"active": true,
|
|
"nickname": null,
|
|
"role": "admin",
|
|
"hobbies": ["math", "lace"],
|
|
"address": { "city": "London", "zip": 12345 },
|
|
"friends": [
|
|
{ "name": "Bob" },
|
|
{ "name": "Cay", "age": 40 }
|
|
],
|
|
"tree": {
|
|
"value": 1,
|
|
"children": [ { "value": 2 }, { "value": 3 } ],
|
|
"next": { "value": 99 }
|
|
},
|
|
"extra_unknown": { "ignored": [1, 2, {"x": "y"}] }
|
|
})";
|
|
// Root is strict (additionalProperties:false), so "extra_unknown" must
|
|
// make it reject. Verify that, then test a clean version.
|
|
expectReject(json, "unknown key in strict root");
|
|
}
|
|
|
|
// ---- invalid stack size is rejected without crashing ----
|
|
{
|
|
RootBuilder b(-1);
|
|
char buf[] = "null";
|
|
WeaselJsonStatus s = b.feed(buf, sizeof(buf) - 1);
|
|
CHECK(s == WeaselJson_REJECT);
|
|
printf("ok invalid stack size rejected, not crashed\n");
|
|
}
|
|
|
|
{
|
|
std::string json = R"({
|
|
"name": "Ada É",
|
|
"age": 36,
|
|
"height": 1.75,
|
|
"active": true,
|
|
"nickname": null,
|
|
"role": "admin",
|
|
"hobbies": ["math", "lace"],
|
|
"address": { "city": "London", "zip": 12345 },
|
|
"friends": [
|
|
{ "name": "Bob" },
|
|
{ "name": "Cay", "age": 40 }
|
|
],
|
|
"tree": {
|
|
"value": 1,
|
|
"children": [ { "value": 2 }, { "value": 3 } ],
|
|
"next": { "value": 99 }
|
|
},
|
|
"nullable_hobbies": [null, "math", null, "lace", null],
|
|
"nullable_scores": [null, 10, null, 20, null]
|
|
})";
|
|
RootBuilder b;
|
|
WeaselJsonStatus s = parseStrided(b, json);
|
|
CHECK(s == WeaselJson_OK);
|
|
if (s == WeaselJson_OK) {
|
|
Root r = b.take();
|
|
CHECK(r.name == "Ada \xC3\x89"); // É -> UTF-8
|
|
CHECK(r.age == 36);
|
|
CHECK(r.height > 1.74 && r.height < 1.76);
|
|
CHECK(r.active.has_value() && *r.active == true);
|
|
CHECK(!r.nickname.has_value()); // explicit null
|
|
CHECK(r.role.has_value() && *r.role == Role::admin);
|
|
CHECK(r.hobbies.has_value() && r.hobbies->size() == 2);
|
|
CHECK(r.hobbies && (*r.hobbies)[0] == "math" &&
|
|
(*r.hobbies)[1] == "lace");
|
|
CHECK(r.address.has_value() && r.address->city == "London");
|
|
CHECK(r.address && r.address->zip.has_value() &&
|
|
*r.address->zip == 12345);
|
|
CHECK(r.friends.has_value() && r.friends->size() == 2);
|
|
CHECK(r.friends && (*r.friends)[0].name == "Bob");
|
|
CHECK(r.friends && !(*r.friends)[0].age.has_value());
|
|
CHECK(r.friends && (*r.friends)[1].age.has_value() &&
|
|
*(*r.friends)[1].age == 40);
|
|
CHECK(r.tree.has_value() && r.tree->value == 1);
|
|
CHECK(r.tree && r.tree->children.has_value() &&
|
|
r.tree->children->size() == 2);
|
|
CHECK(r.tree && r.tree->children && (*r.tree->children)[0].value == 2);
|
|
CHECK(r.tree && r.tree->children && (*r.tree->children)[1].value == 3);
|
|
CHECK(r.tree && r.tree->next && r.tree->next->value == 99);
|
|
CHECK(r.nullable_hobbies.has_value() && r.nullable_hobbies->size() == 5);
|
|
CHECK(r.nullable_hobbies && !(*r.nullable_hobbies)[0] &&
|
|
(*r.nullable_hobbies)[1] && *(*r.nullable_hobbies)[1] == "math" &&
|
|
!(*r.nullable_hobbies)[2] && (*r.nullable_hobbies)[3] &&
|
|
*(*r.nullable_hobbies)[3] == "lace" && !(*r.nullable_hobbies)[4]);
|
|
CHECK(r.nullable_scores.has_value() && r.nullable_scores->size() == 5);
|
|
CHECK(r.nullable_scores && !(*r.nullable_scores)[0] &&
|
|
(*r.nullable_scores)[1] && *(*r.nullable_scores)[1] == 10 &&
|
|
!(*r.nullable_scores)[2] && (*r.nullable_scores)[3] &&
|
|
*(*r.nullable_scores)[3] == 20 && !(*r.nullable_scores)[4]);
|
|
printf("ok happy path (byte-strided)\n");
|
|
}
|
|
}
|
|
|
|
// ---- whole-buffer feed (not strided) ----
|
|
{
|
|
std::string json = R"({"name":"x","age":7})";
|
|
RootBuilder b;
|
|
WeaselJsonStatus s = b.feed(json.data(), (int)json.size());
|
|
if (s == WeaselJson_AGAIN)
|
|
s = b.finish();
|
|
CHECK(s == WeaselJson_OK);
|
|
Root r = b.take();
|
|
CHECK(r.name == "x" && r.age == 7);
|
|
CHECK(!r.role.has_value() && !r.hobbies.has_value());
|
|
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)");
|
|
expectReject(R"({"name":"x","age":1,"role":"boss"})", "bad enum value");
|
|
expectReject(R"({"name":"x","age":1,"name":"y"})", "duplicate key");
|
|
expectReject(R"({"name":"x","age":1,"active":null})",
|
|
"null for non-nullable");
|
|
expectReject(R"({"name":"x","age":1,"age":2})", "duplicate required key");
|
|
expectReject(R"({"name":"x","age":1.5})", "fractional for integer");
|
|
expectReject(R"({"name":"x","age":1,"address":{"zip":5}})",
|
|
"missing required nested 'city'");
|
|
expectReject(R"([1,2,3])", "array where object expected (root)");
|
|
expectReject("null", "null where object expected (root)");
|
|
expectReject("true", "boolean where object expected (root)");
|
|
expectReject("123", "number where object expected (root)");
|
|
expectReject(R"("hi")", "string where object expected (root)");
|
|
expectReject(R"({"name":"x","age":1,)", "truncated / invalid json");
|
|
|
|
if (failures == 0) {
|
|
printf("\nALL TESTS PASSED\n");
|
|
return 0;
|
|
}
|
|
printf("\n%d FAILURE(S)\n", failures);
|
|
return 1;
|
|
}
|