// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // Describe gives `crafter-debug help` what an agent needs to call a command // right the first time: each field's name and type, its default taken from // the member initializer, Required fields marked with no default, a narrow // integer's range, an enum's names, a nested aggregate's own fields. A type // that contains itself is described a few levels deep instead of forever. import std; import Crafter.Debug.Core; using namespace Crafter::Debug; namespace Game { enum class Mode : std::uint8_t { Idle, Build }; struct Vec3 { float x = 0; float y = 1; float z = 0; }; struct Aim { Required slot; float yaw = 45; Vec3 at; std::optional label; Mode mode = Mode::Build; std::vector ids; }; struct Node { std::vector children; }; } 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() { const Value description = Describe(); std::println("{}", description.DumpPretty()); Check(description.Find("type")->AsString() == "object" && description.Find("name")->AsString().ends_with("Aim"), "an aggregate describes as a named object"); const std::span fields = description.Find("fields")->Items(); Check(fields.size() == 6, "one entry per field"); const auto field = [&](std::string_view name) -> const Value& { for (const Value& f : fields) { if (f.Find("name")->AsString() == name) return f; } throw std::runtime_error(std::format("no field {}", name)); }; Check(field("slot").Find("required") != nullptr && field("slot").Find("default") == nullptr, "a Required field is marked required, with no default"); Check(field("slot").Find("type")->Find("maximum")->AsInteger() == 255, "a narrow integer carries its range"); Check(field("yaw").Find("default")->AsNumber() == 45, "a default comes from the member initializer"); Check(field("at").Find("type")->Find("fields")->Size() == 3 && field("at").Find("default")->Find("y")->AsNumber() == 1, "a nested aggregate describes its fields and default"); Check(field("label").Find("type")->Find("nullable") != nullptr && field("label").Find("default")->IsNull(), "an optional is nullable and defaults to null"); Check(field("mode").Find("type")->Find("values")->Size() == 2 && field("mode").Find("default")->AsString() == "Build", "an enum lists its names"); Check(field("ids").Find("type")->Find("items")->Find("type")->AsString() == "integer", "a vector describes its items"); Check(!Describe().Dump().empty(), "a type that contains itself is described without recursing forever"); return failures == 0 ? 0 : 1; }