// Test harness for the generated example.schema.json parser. // Feeds input one byte at a time to exercise chunked string/number paths. #include #include #include #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; }