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
|
required for the VN surface, while `norn.graph` and `tex.math` are
|
||||||
`optional-nested` independent sidecar plugin projections. This keeps
|
`optional-nested` independent sidecar plugin projections. This keeps
|
||||||
runtime projection support from becoming an implied Sai dependency story.
|
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,
|
- Sai, Norn, and TeX now have plugin ABI fixtures for describe, validate,
|
||||||
project, lower, measure, and apply behavior. The parity harness validates
|
project, lower, measure, and apply behavior. The parity harness validates
|
||||||
those fixtures against plugin manifests, so Eve proves the ABI shape without
|
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 = [];
|
const errors = [];
|
||||||
if (!schema || typeof schema !== "object") return 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) {
|
if (schema.const !== undefined && value !== schema.const) {
|
||||||
errors.push(`${pointer}:const:${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)) {
|
if (schema.type && !matchesSchemaType(value, schema.type)) {
|
||||||
errors.push(`${pointer}:type:${schema.type}`);
|
errors.push(`${pointer}:type:${schema.type}`);
|
||||||
return errors;
|
return errors;
|
||||||
|
|
@ -23,18 +33,26 @@ export function validateSchemaSubset(schema, value, pointer = "$") {
|
||||||
if (schema.properties && value && typeof value === "object" && !Array.isArray(value)) {
|
if (schema.properties && value && typeof value === "object" && !Array.isArray(value)) {
|
||||||
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
||||||
if (value[key] !== undefined) {
|
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)) {
|
if (schema.items && Array.isArray(value)) {
|
||||||
value.forEach((item, index) => {
|
value.forEach((item, index) => {
|
||||||
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`));
|
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`, rootSchema));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return errors;
|
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) {
|
function matchesSchemaType(value, type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "array":
|
case "array":
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,84 @@
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"root": {
|
"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",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
|
|
@ -85,32 +163,17 @@
|
||||||
"embeddedDocuments": {
|
"embeddedDocuments": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"$ref": "#/$defs/embeddedDocument"
|
||||||
"required": [
|
|
||||||
"slotId",
|
|
||||||
"documentId"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"slotId": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"documentId": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"schemaId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"presentationKind": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pluginProjection": {
|
||||||
|
"$ref": "#/$defs/pluginProjection"
|
||||||
|
},
|
||||||
"children": {
|
"children": {
|
||||||
"type": "array"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/shellNode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
|
|
|
||||||
|
|
@ -84,52 +84,7 @@
|
||||||
"pluginProjections": {
|
"pluginProjections": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"$ref": "#/$defs/pluginProjection"
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lossiness": {
|
"lossiness": {
|
||||||
|
|
@ -137,5 +92,66 @@
|
||||||
"minLength": 1
|
"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
|
"additionalProperties": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,87 @@
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"root": {
|
"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",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
|
|
@ -89,35 +170,17 @@
|
||||||
"embeddedDocuments": {
|
"embeddedDocuments": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"$ref": "#/$defs/embeddedDocument"
|
||||||
"required": [
|
|
||||||
"slotId",
|
|
||||||
"documentId"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"slotId": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"documentId": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"schemaId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"presentationKind": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginProjection": {
|
"pluginProjection": {
|
||||||
"type": "object"
|
"$ref": "#/$defs/pluginProjection"
|
||||||
},
|
},
|
||||||
"children": {
|
"children": {
|
||||||
"type": "array"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/sceneNode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,87 @@
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"root": {
|
"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",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
|
|
@ -96,35 +177,17 @@
|
||||||
"embeddedDocuments": {
|
"embeddedDocuments": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"$ref": "#/$defs/embeddedDocument"
|
||||||
"required": [
|
|
||||||
"slotId",
|
|
||||||
"documentId"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"slotId": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"documentId": {
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1
|
|
||||||
},
|
|
||||||
"schemaId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"presentationKind": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pluginProjection": {
|
"pluginProjection": {
|
||||||
"type": "object"
|
"$ref": "#/$defs/pluginProjection"
|
||||||
},
|
},
|
||||||
"children": {
|
"children": {
|
||||||
"type": "array"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/visualElementNode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"additionalProperties": true
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
|
||||||
import { buildUnitySceneCaptureArtifact, buildUnitySceneProjection } from "./eveunity-scene-capture-artifact.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 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 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"));
|
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.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.schema, "gamecult.eve.unity_scene_projection.v1");
|
||||||
assert.equal(projection.runtimeId, "unity-scene");
|
assert.equal(projection.runtimeId, "unity-scene");
|
||||||
assert.equal(projection.providerId, "aetheria");
|
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");
|
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 graphNode = projection.root.children.find(child => child.id === "sai.graph");
|
||||||
const texLayer = projection.root.children.find(child => child.id === "sai.embeds");
|
const texLayer = projection.root.children.find(child => child.id === "sai.embeds");
|
||||||
const texNode = texLayer.children.find(child => child.id === "sai.tex.log-power");
|
const texNode = texLayer.children.find(child => child.id === "sai.tex.log-power");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs";
|
||||||
import { buildUnityUiToolkitCaptureArtifact, buildUnityUiToolkitProjection } from "./eveunity-uitoolkit-capture-artifact.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 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 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"));
|
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.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.schema, "gamecult.eve.unity_uitoolkit_projection.v1");
|
||||||
assert.equal(projection.runtimeId, "unity-uitoolkit");
|
assert.equal(projection.runtimeId, "unity-uitoolkit");
|
||||||
assert.equal(projection.providerId, "aetheria");
|
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");
|
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 graphNode = projection.root.children.find(child => child.id === "sai.graph");
|
||||||
const texLayer = projection.root.children.find(child => child.id === "sai.embeds");
|
const texLayer = projection.root.children.find(child => child.id === "sai.embeds");
|
||||||
const texNode = texLayer.children.find(child => child.id === "sai.tex.log-power");
|
const texNode = texLayer.children.find(child => child.id === "sai.tex.log-power");
|
||||||
|
|
|
||||||
|
|
@ -1242,13 +1242,23 @@ function extractProviderSchemaIds(schemas) {
|
||||||
}).filter(Boolean).sort();
|
}).filter(Boolean).sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateSchemaSubset(schema, value, pointer = "$") {
|
function validateSchemaSubset(schema, value, pointer = "$", rootSchema = schema) {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
if (!schema || typeof schema !== "object") return 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) {
|
if (schema.const !== undefined && value !== schema.const) {
|
||||||
errors.push(`${pointer}:const:${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)) {
|
if (schema.type && !matchesSchemaType(value, schema.type)) {
|
||||||
errors.push(`${pointer}:type:${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) {
|
if (schema.minLength !== undefined && typeof value === "string" && value.length < schema.minLength) {
|
||||||
errors.push(`${pointer}:minLength:${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)) {
|
if (schema.required && value && typeof value === "object" && !Array.isArray(value)) {
|
||||||
for (const key of schema.required) {
|
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)) {
|
if (schema.properties && value && typeof value === "object" && !Array.isArray(value)) {
|
||||||
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
||||||
if (value[key] !== undefined) {
|
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)) {
|
if (schema.items && Array.isArray(value)) {
|
||||||
value.forEach((item, index) => {
|
value.forEach((item, index) => {
|
||||||
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`));
|
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`, rootSchema));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
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) {
|
function matchesSchemaType(value, type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "array":
|
case "array":
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue