Own witness schema validation
This commit is contained in:
parent
cd4fc7fad0
commit
35ac28e116
3 changed files with 77 additions and 4 deletions
|
|
@ -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("; ")}`);
|
||||
|
||||
|
|
|
|||
|
|
@ -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("; ")}`);
|
||||
|
||||
|
|
|
|||
71
tools/lib/schema-subset.mjs
Normal file
71
tools/lib/schema-subset.mjs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in a new issue