schemagen: fix nullable root types (#13)
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 52s
CI / pre-commit (pull_request) Successful in 54s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 47s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m27s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 52s
CI / pre-commit (pull_request) Successful in 54s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 47s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m27s
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
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
// Regression test for issue #13: nullable root types.
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "nullable_array.h"
|
||||
#include "nullable_object.h"
|
||||
#include "nullable_string.h"
|
||||
|
||||
static int failures = 0;
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||
++failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static WeaselJsonStatus parseStrided(nullable_object::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 WeaselJsonStatus parseStrided(nullable_string::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 WeaselJsonStatus parseStrided(nullable_array::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 void expectReject(nullable_object::RootBuilder &b, std::string in,
|
||||
const char *what) {
|
||||
WeaselJsonStatus s = parseStrided(b, in);
|
||||
if (s == WeaselJson_REJECT) {
|
||||
printf("ok reject: %s\n", what);
|
||||
} else {
|
||||
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
|
||||
in.c_str());
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// ---- nullable root object: valid document ----
|
||||
{
|
||||
nullable_object::RootBuilder b;
|
||||
WeaselJsonStatus s = parseStrided(b, R"({"x":"hello"})");
|
||||
CHECK(s == WeaselJson_OK);
|
||||
if (s == WeaselJson_OK) {
|
||||
nullable_object::Root r = b.take();
|
||||
CHECK(r.has_value());
|
||||
CHECK(r->x == "hello");
|
||||
printf("ok nullable root object accepts object\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- nullable root object: null document ----
|
||||
{
|
||||
nullable_object::RootBuilder b;
|
||||
WeaselJsonStatus s = parseStrided(b, "null");
|
||||
CHECK(s == WeaselJson_OK);
|
||||
if (s == WeaselJson_OK) {
|
||||
nullable_object::Root r = b.take();
|
||||
CHECK(!r.has_value());
|
||||
printf("ok nullable root object accepts null\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- nullable root object: schema checks still run ----
|
||||
{
|
||||
nullable_object::RootBuilder b;
|
||||
expectReject(b, R"({"x":"hello","extra":1})",
|
||||
"unknown key in strict nullable root object");
|
||||
}
|
||||
{
|
||||
nullable_object::RootBuilder b;
|
||||
expectReject(b, R"({})", "missing required field in nullable root object");
|
||||
}
|
||||
|
||||
// ---- nullable root string: valid value ----
|
||||
{
|
||||
nullable_string::RootBuilder b;
|
||||
WeaselJsonStatus s = parseStrided(b, R"("hello")");
|
||||
CHECK(s == WeaselJson_OK);
|
||||
if (s == WeaselJson_OK) {
|
||||
nullable_string::Root r = b.take();
|
||||
CHECK(r.has_value());
|
||||
CHECK(*r == "hello");
|
||||
printf("ok nullable root string accepts string\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- nullable root string: null value ----
|
||||
{
|
||||
nullable_string::RootBuilder b;
|
||||
WeaselJsonStatus s = parseStrided(b, "null");
|
||||
CHECK(s == WeaselJson_OK);
|
||||
if (s == WeaselJson_OK) {
|
||||
nullable_string::Root r = b.take();
|
||||
CHECK(!r.has_value());
|
||||
printf("ok nullable root string accepts null\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- nullable root array: valid value ----
|
||||
{
|
||||
nullable_array::RootBuilder b;
|
||||
WeaselJsonStatus s = parseStrided(b, "[1,2,3]");
|
||||
CHECK(s == WeaselJson_OK);
|
||||
if (s == WeaselJson_OK) {
|
||||
nullable_array::Root r = b.take();
|
||||
CHECK(r.has_value());
|
||||
CHECK(r->size() == 3);
|
||||
CHECK((*r)[0] == 1 && (*r)[1] == 2 && (*r)[2] == 3);
|
||||
printf("ok nullable root array accepts array\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- nullable root array: null value ----
|
||||
{
|
||||
nullable_array::RootBuilder b;
|
||||
WeaselJsonStatus s = parseStrided(b, "null");
|
||||
CHECK(s == WeaselJson_OK);
|
||||
if (s == WeaselJson_OK) {
|
||||
nullable_array::Root r = b.take();
|
||||
CHECK(!r.has_value());
|
||||
printf("ok nullable root array accepts null\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (failures == 0) {
|
||||
printf("\nALL TESTS PASSED\n");
|
||||
return 0;
|
||||
}
|
||||
printf("\n%d FAILURE(S)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user