Own witness schema validation

This commit is contained in:
Meta Vens 2026-07-10 10:30:19 +01:00
commit 35ac28e116
3 changed files with 77 additions and 4 deletions

View file

@ -1,7 +1,7 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
import { validateSchemaSubset } from "../lib/schema-subset.mjs";
const [witnessArgument, exportArgument] = process.argv.slice(2);
if (!witnessArgument || !exportArgument) {
@ -12,8 +12,9 @@ const witnessPath = path.resolve(witnessArgument);
const exportDirectory = path.resolve(exportArgument);
const indexPath = path.join(exportDirectory, "index.json");
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const kernelRoot = path.resolve(process.env.EVE_KERNEL_ROOT || "E:\\Projects\\Eve");
const witness = parseJson(await readFile(witnessPath, "utf8"));
const schema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.plugin_witness.v1.schema.json"), "utf8"));
const schema = parseJson(await readFile(path.join(kernelRoot, "schemas/gamecult.eve.plugin_witness.v1.schema.json"), "utf8"));
const schemaErrors = validateSchemaSubset(schema, witness);
if (schemaErrors.length) throw new Error(`Plugin witness schema errors: ${schemaErrors.join("; ")}`);

View file

@ -1,7 +1,7 @@
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
import { validateSchemaSubset } from "../lib/schema-subset.mjs";
const [witnessArgument, exportArgument] = process.argv.slice(2);
if (!witnessArgument || !exportArgument) {
@ -15,7 +15,8 @@ const indexPath = path.join(exportDirectory, "index.json");
const witness = parseJson(await readFile(witnessPath, "utf8"));
validateWitness(witness);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const witnessSchema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.runtime_witness.v1.schema.json"), "utf8"));
const kernelRoot = path.resolve(process.env.EVE_KERNEL_ROOT || "E:\\Projects\\Eve");
const witnessSchema = parseJson(await readFile(path.join(kernelRoot, "schemas/gamecult.eve.runtime_witness.v1.schema.json"), "utf8"));
const schemaErrors = validateSchemaSubset(witnessSchema, witness);
if (schemaErrors.length) throw new Error(`Runtime witness schema errors: ${schemaErrors.join("; ")}`);

View file

@ -0,0 +1,71 @@
export function validateSchemaSubset(schema, value, pointer = "$", rootSchema = schema) {
const errors = [];
if (!schema || typeof schema !== "object") return errors;
if (schema.$ref) {
const resolved = resolveSchemaReference(rootSchema, schema.$ref);
return resolved
? validateSchemaSubset(resolved, value, pointer, rootSchema)
: [`${pointer}:unresolved-ref:${schema.$ref}`];
}
if (schema.const !== undefined && value !== schema.const) {
errors.push(`${pointer}:const:${schema.const}`);
}
if (schema.enum && !schema.enum.includes(value)) {
errors.push(`${pointer}:enum:${schema.enum.join("|")}`);
}
if (schema.type && !matchesSchemaType(value, schema.type)) {
errors.push(`${pointer}:type:${schema.type}`);
return errors;
}
if (schema.minLength !== undefined && typeof value === "string" && value.length < schema.minLength) {
errors.push(`${pointer}:minLength:${schema.minLength}`);
}
if (schema.minItems !== undefined && Array.isArray(value) && value.length < schema.minItems) {
errors.push(`${pointer}:minItems:${schema.minItems}`);
}
if (schema.required && value && typeof value === "object" && !Array.isArray(value)) {
for (const key of schema.required) {
if (value[key] === undefined) errors.push(`${pointer}.${key}:required`);
}
}
if (schema.properties && value && typeof value === "object" && !Array.isArray(value)) {
for (const [key, childSchema] of Object.entries(schema.properties)) {
if (value[key] !== undefined) {
errors.push(...validateSchemaSubset(childSchema, value[key], `${pointer}.${key}`, rootSchema));
}
}
}
if (schema.items && Array.isArray(value)) {
value.forEach((item, index) => {
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`, rootSchema));
});
}
return errors;
}
function resolveSchemaReference(rootSchema, reference) {
if (!reference.startsWith("#/")) return null;
return reference.slice(2).split("/").reduce((current, segment) => {
if (!current || typeof current !== "object") return null;
return current[segment.replaceAll("~1", "/").replaceAll("~0", "~")];
}, rootSchema);
}
function matchesSchemaType(value, type) {
switch (type) {
case "array":
return Array.isArray(value);
case "object":
return value !== null && typeof value === "object" && !Array.isArray(value);
case "string":
return typeof value === "string";
case "number":
return typeof value === "number";
case "boolean":
return typeof value === "boolean";
default:
return true;
}
}