mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 09:33:51 +00:00
feat(editor): add --validate-jsondbc for strict JSON DBC schema check
--info-jsondbc only verifies recordCount matches the actual records[] array length. This goes deeper, validating the full sidecar schema that --convert-json-dbc consumes: wowee_editor --validate-jsondbc db/Spell.json Checks: - top-level value is a JSON object - 'format' field exists, is a string, equals 'wowee-dbc-json-1.0' - 'source' field present (so re-import knows the DBC slot) - recordCount + fieldCount are non-negative integers - 'records' is an array; recordCount matches actual length - each record is an array exactly fieldCount cells wide - each cell is string|number|bool|null (no nested objects/arrays) Errors capped (3 per category) with '... and N more' tail so a 1000-row file with consistent breakage doesn't drown the report. Exit 1 on any error so CI can gate. Verified on a hand-rolled good JSON (passes clean) and a bad one with: wrong format tag, missing source, wrong-width row, and an object cell — all 4 issues reported with precise positions and exit 1. Format-validator lineup is now complete: Open binary: WOM / WOB / WOC / WHM / GLB Open text: JSON DBC Every shippable open format has a CLI validator that gates on schema/structure errors.
This commit is contained in:
parent
fab6238e64
commit
b3e34e0edf
1 changed files with 149 additions and 0 deletions
|
|
@ -500,6 +500,8 @@ static void printUsage(const char* argv0) {
|
|||
std::printf(" Recursively run all per-format validators on every file\n");
|
||||
std::printf(" --validate-glb <path> [--json]\n");
|
||||
std::printf(" Verify a glTF 2.0 binary's structure (magic, chunks, JSON, accessors)\n");
|
||||
std::printf(" --validate-jsondbc <path> [--json]\n");
|
||||
std::printf(" Verify a JSON DBC sidecar's full schema (per-cell types, row width, format tag)\n");
|
||||
std::printf(" --info-glb <path> [--json]\n");
|
||||
std::printf(" Print glTF 2.0 binary metadata (chunks, mesh/primitive counts, accessors)\n");
|
||||
std::printf(" --zone-summary <zoneDir> [--json]\n");
|
||||
|
|
@ -602,6 +604,7 @@ int main(int argc, char* argv[]) {
|
|||
"--unpack-wcp", "--pack-wcp",
|
||||
"--validate", "--validate-wom", "--validate-wob", "--validate-woc",
|
||||
"--validate-whm", "--validate-all", "--validate-glb", "--info-glb",
|
||||
"--validate-jsondbc",
|
||||
"--zone-summary",
|
||||
"--export-zone-summary-md", "--export-quest-graph",
|
||||
"--scaffold-zone", "--add-tile", "--remove-tile", "--list-tiles",
|
||||
|
|
@ -3681,6 +3684,152 @@ int main(int argc, char* argv[]) {
|
|||
std::printf(" FAILED — %d error(s):\n", errorCount);
|
||||
for (const auto& e : errors) std::printf(" - %s\n", e.c_str());
|
||||
return isValidate ? 1 : 0;
|
||||
} else if (std::strcmp(argv[i], "--validate-jsondbc") == 0 && i + 1 < argc) {
|
||||
// Strict schema validator for JSON DBC sidecars. --info-jsondbc
|
||||
// checks that header recordCount matches the actual records[]
|
||||
// length; this goes deeper:
|
||||
// - format tag is the wowee 1.0 string
|
||||
// - source field present (so re-import knows which DBC slot)
|
||||
// - recordCount + fieldCount are non-negative integers
|
||||
// - records is an array
|
||||
// - each record is an array exactly fieldCount long
|
||||
// - each cell is string|number|bool|null (no objects/arrays)
|
||||
// Catches the kind of corruption that load() might silently
|
||||
// tolerate (missing fields default to 0/empty), letting the
|
||||
// editor's runtime DBC loader downstream-fail in confusing
|
||||
// ways.
|
||||
std::string path = argv[++i];
|
||||
bool jsonOut = (i + 1 < argc &&
|
||||
std::strcmp(argv[i + 1], "--json") == 0);
|
||||
if (jsonOut) i++;
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
std::fprintf(stderr,
|
||||
"validate-jsondbc: cannot open %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
nlohmann::json doc;
|
||||
std::vector<std::string> errors;
|
||||
try {
|
||||
in >> doc;
|
||||
} catch (const std::exception& e) {
|
||||
errors.push_back(std::string("JSON parse error: ") + e.what());
|
||||
}
|
||||
std::string format, source;
|
||||
uint32_t recordCount = 0, fieldCount = 0;
|
||||
uint32_t actualRecs = 0;
|
||||
int badRowWidths = 0, badCellTypes = 0;
|
||||
if (errors.empty()) {
|
||||
if (!doc.is_object()) {
|
||||
errors.push_back("top-level value is not a JSON object");
|
||||
} else {
|
||||
if (!doc.contains("format")) {
|
||||
errors.push_back("missing 'format' field");
|
||||
} else if (!doc["format"].is_string()) {
|
||||
errors.push_back("'format' field is not a string");
|
||||
} else {
|
||||
format = doc["format"].get<std::string>();
|
||||
if (format != "wowee-dbc-json-1.0") {
|
||||
errors.push_back("'format' is '" + format +
|
||||
"', expected 'wowee-dbc-json-1.0'");
|
||||
}
|
||||
}
|
||||
if (!doc.contains("source")) {
|
||||
errors.push_back("missing 'source' field (re-import needs it)");
|
||||
} else {
|
||||
source = doc.value("source", std::string{});
|
||||
}
|
||||
if (!doc.contains("recordCount") ||
|
||||
!doc["recordCount"].is_number_integer()) {
|
||||
errors.push_back("'recordCount' missing or not an integer");
|
||||
} else {
|
||||
recordCount = doc["recordCount"].get<uint32_t>();
|
||||
}
|
||||
if (!doc.contains("fieldCount") ||
|
||||
!doc["fieldCount"].is_number_integer()) {
|
||||
errors.push_back("'fieldCount' missing or not an integer");
|
||||
} else {
|
||||
fieldCount = doc["fieldCount"].get<uint32_t>();
|
||||
}
|
||||
if (!doc.contains("records") || !doc["records"].is_array()) {
|
||||
errors.push_back("'records' missing or not an array");
|
||||
} else {
|
||||
const auto& records = doc["records"];
|
||||
actualRecs = static_cast<uint32_t>(records.size());
|
||||
if (actualRecs != recordCount) {
|
||||
errors.push_back("recordCount " + std::to_string(recordCount) +
|
||||
" != actual records " +
|
||||
std::to_string(actualRecs));
|
||||
}
|
||||
for (size_t r = 0; r < records.size(); ++r) {
|
||||
const auto& row = records[r];
|
||||
if (!row.is_array()) {
|
||||
errors.push_back("record[" + std::to_string(r) +
|
||||
"] is not an array");
|
||||
continue;
|
||||
}
|
||||
if (row.size() != fieldCount) {
|
||||
badRowWidths++;
|
||||
if (badRowWidths <= 3) {
|
||||
errors.push_back("record[" + std::to_string(r) +
|
||||
"] has " + std::to_string(row.size()) +
|
||||
" cells, expected " +
|
||||
std::to_string(fieldCount));
|
||||
}
|
||||
}
|
||||
for (size_t c = 0; c < row.size(); ++c) {
|
||||
const auto& cell = row[c];
|
||||
bool ok = cell.is_string() || cell.is_number() ||
|
||||
cell.is_boolean() || cell.is_null();
|
||||
if (!ok) {
|
||||
badCellTypes++;
|
||||
if (badCellTypes <= 3) {
|
||||
errors.push_back("record[" + std::to_string(r) +
|
||||
"][" + std::to_string(c) +
|
||||
"] has invalid type (objects/arrays not allowed)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (badRowWidths > 3) {
|
||||
errors.push_back("... and " + std::to_string(badRowWidths - 3) +
|
||||
" more rows with wrong cell count");
|
||||
}
|
||||
if (badCellTypes > 3) {
|
||||
errors.push_back("... and " + std::to_string(badCellTypes - 3) +
|
||||
" more cells with invalid types");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int errorCount = static_cast<int>(errors.size());
|
||||
if (jsonOut) {
|
||||
nlohmann::json j;
|
||||
j["jsondbc"] = path;
|
||||
j["format"] = format;
|
||||
j["source"] = source;
|
||||
j["recordCount"] = recordCount;
|
||||
j["fieldCount"] = fieldCount;
|
||||
j["actualRecords"] = actualRecs;
|
||||
j["errorCount"] = errorCount;
|
||||
j["errors"] = errors;
|
||||
j["passed"] = errors.empty();
|
||||
std::printf("%s\n", j.dump(2).c_str());
|
||||
return errors.empty() ? 0 : 1;
|
||||
}
|
||||
std::printf("JSON DBC: %s\n", path.c_str());
|
||||
std::printf(" format : %s\n", format.empty() ? "?" : format.c_str());
|
||||
std::printf(" source : %s\n", source.empty() ? "?" : source.c_str());
|
||||
std::printf(" records : %u (header) / %u (actual)\n",
|
||||
recordCount, actualRecs);
|
||||
std::printf(" fields : %u\n", fieldCount);
|
||||
if (errors.empty()) {
|
||||
std::printf(" PASSED\n");
|
||||
return 0;
|
||||
}
|
||||
std::printf(" FAILED — %d error(s):\n", errorCount);
|
||||
for (const auto& e : errors) std::printf(" - %s\n", e.c_str());
|
||||
return 1;
|
||||
} else if (std::strcmp(argv[i], "--export-obj") == 0 && i + 1 < argc) {
|
||||
// Convert WOM (our open M2 replacement) to Wavefront OBJ — a
|
||||
// universally supported text format that opens directly in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue