Require plugin availability in projection schemas
This commit is contained in:
parent
e74f52ab0d
commit
4bee5c8504
9 changed files with 381 additions and 123 deletions
|
|
@ -974,6 +974,12 @@ Recently cut:
|
|||
required for the VN surface, while `norn.graph` and `tex.math` are
|
||||
`optional-nested` independent sidecar plugin projections. This keeps
|
||||
runtime projection support from becoming an implied Sai dependency story.
|
||||
- Runtime projection schemas now type that boundary. Electron, TUI, Unity
|
||||
scene, and Unity UI Toolkit projection schemas require plugin projections to
|
||||
declare `availability`, `semanticOwner`, ABI schema, command boundary, and
|
||||
capability claims; the local schema subset validators now resolve `$ref` and
|
||||
enforce enum values so missing availability cannot hide in recursive runtime
|
||||
capture trees.
|
||||
- Sai, Norn, and TeX now have plugin ABI fixtures for describe, validate,
|
||||
project, lower, measure, and apply behavior. The parity harness validates
|
||||
those fixtures against plugin manifests, so Eve proves the ABI shape without
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
export function validateSchemaSubset(schema, value, pointer = "$") {
|
||||
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;
|
||||
|
|
@ -23,18 +33,26 @@ export function validateSchemaSubset(schema, value, pointer = "$") {
|
|||
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}`));
|
||||
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}]`));
|
||||
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":
|
||||
|
|
|
|||
|
|
@ -46,6 +46,84 @@
|
|||
"minLength": 1
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/$defs/shellNode"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"embeddedDocument": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slotId",
|
||||
"documentId"
|
||||
],
|
||||
"properties": {
|
||||
"slotId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"schemaId": {
|
||||
"type": "string"
|
||||
},
|
||||
"presentationKind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pluginProjection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pluginId",
|
||||
"projectionKind",
|
||||
"abiSchema",
|
||||
"commandBoundary",
|
||||
"capabilities",
|
||||
"semanticOwner",
|
||||
"availability"
|
||||
],
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"abiSchema": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"semanticOwner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"availability": {
|
||||
"type": "string",
|
||||
"enum": ["required", "optional", "optional-nested"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"shellNode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
|
|
@ -85,32 +163,17 @@
|
|||
"embeddedDocuments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slotId",
|
||||
"documentId"
|
||||
],
|
||||
"properties": {
|
||||
"slotId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"schemaId": {
|
||||
"type": "string"
|
||||
},
|
||||
"presentationKind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
"$ref": "#/$defs/embeddedDocument"
|
||||
}
|
||||
},
|
||||
"pluginProjection": {
|
||||
"$ref": "#/$defs/pluginProjection"
|
||||
},
|
||||
"children": {
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/shellNode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
|
|
|||
|
|
@ -84,52 +84,7 @@
|
|||
"pluginProjections": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ownerId",
|
||||
"pluginId",
|
||||
"projectionKind",
|
||||
"semanticOwner",
|
||||
"fallbackKind"
|
||||
],
|
||||
"properties": {
|
||||
"ownerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pluginId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"abiSchema": {
|
||||
"type": "string"
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string"
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"semanticOwner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"fallbackKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
"$ref": "#/$defs/pluginProjection"
|
||||
}
|
||||
},
|
||||
"lossiness": {
|
||||
|
|
@ -137,5 +92,66 @@
|
|||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"pluginProjection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ownerId",
|
||||
"pluginId",
|
||||
"projectionKind",
|
||||
"abiSchema",
|
||||
"commandBoundary",
|
||||
"capabilities",
|
||||
"semanticOwner",
|
||||
"availability",
|
||||
"fallbackKind"
|
||||
],
|
||||
"properties": {
|
||||
"ownerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pluginId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"abiSchema": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"semanticOwner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"availability": {
|
||||
"type": "string",
|
||||
"enum": ["required", "optional", "optional-nested"]
|
||||
},
|
||||
"fallbackKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,87 @@
|
|||
"minLength": 1
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/$defs/sceneNode"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"embeddedDocument": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slotId",
|
||||
"documentId"
|
||||
],
|
||||
"properties": {
|
||||
"slotId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"schemaId": {
|
||||
"type": "string"
|
||||
},
|
||||
"presentationKind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pluginProjection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pluginId",
|
||||
"projectionKind",
|
||||
"abiSchema",
|
||||
"commandBoundary",
|
||||
"capabilities",
|
||||
"semanticOwner",
|
||||
"availability"
|
||||
],
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"abiSchema": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"semanticOwner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"availability": {
|
||||
"type": "string",
|
||||
"enum": ["required", "optional", "optional-nested"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"sceneNode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
|
|
@ -89,35 +170,17 @@
|
|||
"embeddedDocuments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slotId",
|
||||
"documentId"
|
||||
],
|
||||
"properties": {
|
||||
"slotId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"schemaId": {
|
||||
"type": "string"
|
||||
},
|
||||
"presentationKind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
"$ref": "#/$defs/embeddedDocument"
|
||||
}
|
||||
},
|
||||
"pluginProjection": {
|
||||
"type": "object"
|
||||
"$ref": "#/$defs/pluginProjection"
|
||||
},
|
||||
"children": {
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sceneNode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
|
|
|||
|
|
@ -50,6 +50,87 @@
|
|||
"minLength": 1
|
||||
},
|
||||
"root": {
|
||||
"$ref": "#/$defs/visualElementNode"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"embeddedDocument": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slotId",
|
||||
"documentId"
|
||||
],
|
||||
"properties": {
|
||||
"slotId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"schemaId": {
|
||||
"type": "string"
|
||||
},
|
||||
"presentationKind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"pluginProjection": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pluginId",
|
||||
"projectionKind",
|
||||
"abiSchema",
|
||||
"commandBoundary",
|
||||
"capabilities",
|
||||
"semanticOwner",
|
||||
"availability"
|
||||
],
|
||||
"properties": {
|
||||
"pluginId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"abiSchema": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"semanticOwner": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"availability": {
|
||||
"type": "string",
|
||||
"enum": ["required", "optional", "optional-nested"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"visualElementNode": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
|
|
@ -96,35 +177,17 @@
|
|||
"embeddedDocuments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"slotId",
|
||||
"documentId"
|
||||
],
|
||||
"properties": {
|
||||
"slotId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"documentId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"schemaId": {
|
||||
"type": "string"
|
||||
},
|
||||
"presentationKind": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
"$ref": "#/$defs/embeddedDocument"
|
||||
}
|
||||
},
|
||||
"pluginProjection": {
|
||||
"type": "object"
|
||||
"$ref": "#/$defs/pluginProjection"
|
||||
},
|
||||
"children": {
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/visualElementNode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
|
||||
import { buildUnitySceneCaptureArtifact, buildUnitySceneProjection } from "./eveunity-scene-capture-artifact.mjs";
|
||||
|
||||
const unitySceneProjectionSchema = JSON.parse(readFileSync(new URL("../../schemas/gamecult.eve.unity_scene_projection.v1.schema.json", import.meta.url), "utf8"));
|
||||
const aetheriaAdvertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../runtimes/incubating/eve-unity-scene/eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
const aetheriaSurfaceDocument = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria-world-surface.json", import.meta.url), "utf8"));
|
||||
|
|
@ -20,6 +22,7 @@ test("builds Unity scene projection capture artifact from provider advertisement
|
|||
});
|
||||
|
||||
assert.equal(request.artifactPath, "artifacts/eveunity-scene-capture/smoke/unity-scene.png");
|
||||
assert.deepEqual(validateSchemaSubset(unitySceneProjectionSchema, projection), []);
|
||||
assert.equal(projection.schema, "gamecult.eve.unity_scene_projection.v1");
|
||||
assert.equal(projection.runtimeId, "unity-scene");
|
||||
assert.equal(projection.providerId, "aetheria");
|
||||
|
|
@ -43,6 +46,7 @@ test("marks Sai required and Norn TeX optional sidecar plugin projections", () =
|
|||
};
|
||||
|
||||
const projection = buildUnitySceneProjection(saiSurfaceDocument, advertisement, "sai.visual_novel.surface");
|
||||
assert.deepEqual(validateSchemaSubset(unitySceneProjectionSchema, projection), []);
|
||||
const graphNode = projection.root.children.find(child => child.id === "sai.graph");
|
||||
const texLayer = projection.root.children.find(child => child.id === "sai.embeds");
|
||||
const texNode = texLayer.children.find(child => child.id === "sai.tex.log-power");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
|
||||
import { buildUnityUiToolkitCaptureArtifact, buildUnityUiToolkitProjection } from "./eveunity-uitoolkit-capture-artifact.mjs";
|
||||
|
||||
const unityUiToolkitProjectionSchema = JSON.parse(readFileSync(new URL("../../schemas/gamecult.eve.unity_uitoolkit_projection.v1.schema.json", import.meta.url), "utf8"));
|
||||
const aetheriaAdvertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
const aetheriaSurfaceDocument = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria-world-surface.json", import.meta.url), "utf8"));
|
||||
|
|
@ -20,6 +22,7 @@ test("builds Unity UI Toolkit projection capture artifact from provider advertis
|
|||
});
|
||||
|
||||
assert.equal(request.artifactPath, "artifacts/eveunity-uitoolkit-capture/smoke/unity-uitoolkit.png");
|
||||
assert.deepEqual(validateSchemaSubset(unityUiToolkitProjectionSchema, projection), []);
|
||||
assert.equal(projection.schema, "gamecult.eve.unity_uitoolkit_projection.v1");
|
||||
assert.equal(projection.runtimeId, "unity-uitoolkit");
|
||||
assert.equal(projection.providerId, "aetheria");
|
||||
|
|
@ -44,6 +47,7 @@ test("marks Sai required and Norn TeX optional sidecar plugin UI projections", (
|
|||
};
|
||||
|
||||
const projection = buildUnityUiToolkitProjection(saiSurfaceDocument, advertisement, "sai.visual_novel.surface");
|
||||
assert.deepEqual(validateSchemaSubset(unityUiToolkitProjectionSchema, projection), []);
|
||||
const graphNode = projection.root.children.find(child => child.id === "sai.graph");
|
||||
const texLayer = projection.root.children.find(child => child.id === "sai.embeds");
|
||||
const texNode = texLayer.children.find(child => child.id === "sai.tex.log-power");
|
||||
|
|
|
|||
|
|
@ -1242,13 +1242,23 @@ function extractProviderSchemaIds(schemas) {
|
|||
}).filter(Boolean).sort();
|
||||
}
|
||||
|
||||
function validateSchemaSubset(schema, value, pointer = "$") {
|
||||
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}`);
|
||||
|
|
@ -1258,6 +1268,9 @@ function validateSchemaSubset(schema, value, pointer = "$") {
|
|||
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) {
|
||||
|
|
@ -1268,20 +1281,28 @@ function validateSchemaSubset(schema, value, pointer = "$") {
|
|||
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}`));
|
||||
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}]`));
|
||||
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":
|
||||
|
|
|
|||
Loading…
Reference in a new issue