// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Codec round-trips an aggregate built from every supported field type // (strings, nested aggregates in a vector, optional, std::array, string-keyed // maps, a scoped enum, Required) through Value and its text, with no // hand-written serialization. Fields the input leaves out keep their default // member initializers, null clears an optional, and an enum also accepts its // integer. Guards the reflection that command arguments and results rely on. import std; import Crafter.Debug.Core; using namespace Crafter::Debug; namespace Game { enum class Mode : std::uint8_t { Idle, Build, Fight = 5 }; struct Vec3 { float x = 0; float y = 0; float z = 0; }; struct Loadout { std::string name = "default"; std::vector points; std::optional target; std::array flags{}; std::map weights; Mode mode = Mode::Idle; Required slot; }; } namespace { std::int32_t failures = 0; void Check(bool ok, std::string_view what) { std::println("{} {}", ok ? "PASS" : "FAIL", what); if (!ok) ++failures; } } int main() { Game::Loadout in; in.name = "fort \"one\""; in.points = { { 1, 2, 3 }, { -0.5F, 0, 1e10F } }; in.target = -7; in.flags = { true, false, true }; in.weights = { { "cannon", 0.25 }, { "laser", 1 } }; in.mode = Game::Mode::Fight; *in.slot = 3; const Value encoded = Encode(in); Check(encoded.IsObject() && encoded.Size() == 7, "an aggregate encodes as an object with one member per field"); Check(encoded.Find("mode")->AsString() == "Fight", "a scoped enum encodes by name"); Check(encoded.Find("slot")->AsInteger() == 3, "Required encodes as its value"); Check(encoded.Find("target")->AsInteger() == -7, "an optional with a value encodes as the value"); Check(encoded.Find("points")->Items()[1].Find("z")->AsNumber() == 1e10F, "nested aggregates encode inside a vector"); Game::Loadout out; DecodeContext context; Decode(Value::Parse(encoded.Dump()), out, context); Check(context.Ok(), std::format("decodes what it encoded ({})", context.Report())); Check(out.name == in.name && out.points.size() == 2 && out.points[1].z == 1e10F && out.target == -7 && out.flags[2] && out.weights.at("laser") == 1 && out.mode == Game::Mode::Fight && *out.slot == 3, "every field survives the round trip"); Game::Loadout partial; partial.target = 9; DecodeContext partialContext; Decode(Value::Parse(R"({"slot": 1, "target": null, "mode": 1})"), partial, partialContext); Check(partialContext.Ok(), std::format("a partial object decodes ({})", partialContext.Report())); Check(partial.name == "default" && partial.points.empty(), "fields left out keep their defaults"); Check(!partial.target.has_value(), "null clears an optional"); Check(partial.mode == Game::Mode::Build, "an enum accepts its integer"); float nan = 0; DecodeContext nanContext; Decode(Encode(std::numeric_limits::quiet_NaN()), nan, nanContext); Check(nanContext.Ok() && std::isnan(nan), "NaN survives the round trip"); std::vector bits; DecodeContext bitsContext; Decode(Value::Parse("[true,false,true]"), bits, bitsContext); Check(bitsContext.Ok() && bits.size() == 3 && bits[2] && !bits[1], "std::vector decodes"); const std::uint64_t large = (std::uint64_t{ 1 } << 63) + 2048; std::uint64_t decoded = 0; DecodeContext largeContext; Decode(Value::Parse(Encode(large).Dump()), decoded, largeContext); Check(largeContext.Ok() && decoded == large, "a uint64 above the int64 range survives"); return failures == 0 ? 0 : 1; }