From f13a04dd44af0e69c94de7ff22f46ee494956eef Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Thu, 2 May 2024 22:47:42 +0200 Subject: [PATCH] Revert instance as a class --- annotations/index.d.ts | 9 +- annotations/index.js | 47 +++- annotations/index.spec.ts | 14 +- bundle/generate-snapshots.js | 4 +- bundle/test-suite.spec.ts | 4 +- bundle/test-utils.js | 6 +- draft-04/additionalItems.js | 7 +- draft-04/dependencies.js | 5 +- draft-04/items.js | 11 +- draft-04/json-schema-test-suite.spec.ts | 3 +- draft-04/maximum.js | 5 +- draft-04/minimum.js | 5 +- draft-06/contains.js | 3 +- draft-06/json-schema-test-suite.spec.ts | 3 +- draft-07/json-schema-test-suite.spec.ts | 3 +- draft-2019-09/json-schema-test-suite.spec.ts | 3 +- draft-2020-12/json-schema-test-suite.spec.ts | 3 +- lib/core.js | 6 +- lib/index.d.ts | 7 +- lib/instance.d.ts | 30 +++ lib/instance.js | 158 ++++++++++++++ lib/json-node.d.ts | 23 -- lib/json-node.js | 212 ------------------- lib/keywords/additionalProperties.js | 17 +- lib/keywords/const.js | 3 +- lib/keywords/contains.js | 9 +- lib/keywords/contentEncoding.js | 3 +- lib/keywords/contentMediaType.js | 3 +- lib/keywords/contentSchema.js | 3 +- lib/keywords/default.js | 3 +- lib/keywords/dependentRequired.js | 5 +- lib/keywords/dependentSchemas.js | 9 +- lib/keywords/deprecated.js | 3 +- lib/keywords/description.js | 3 +- lib/keywords/enum.js | 3 +- lib/keywords/examples.js | 3 +- lib/keywords/exclusiveMaximum.js | 3 +- lib/keywords/exclusiveMinimum.js | 3 +- lib/keywords/format.js | 3 +- lib/keywords/itemPattern.js | 5 +- lib/keywords/items.js | 7 +- lib/keywords/maxItems.js | 5 +- lib/keywords/maxLength.js | 5 +- lib/keywords/maxProperties.js | 3 +- lib/keywords/maximum.js | 3 +- lib/keywords/minItems.js | 5 +- lib/keywords/minLength.js | 5 +- lib/keywords/minProperties.js | 3 +- lib/keywords/minimum.js | 3 +- lib/keywords/multipleOf.js | 5 +- lib/keywords/pattern.js | 5 +- lib/keywords/patternProperties.js | 17 +- lib/keywords/prefixItems.js | 7 +- lib/keywords/properties.js | 19 +- lib/keywords/propertyDependencies.js | 11 +- lib/keywords/propertyNames.js | 5 +- lib/keywords/readOnly.js | 3 +- lib/keywords/requireAllExcept.js | 3 +- lib/keywords/required.js | 3 +- lib/keywords/title.js | 3 +- lib/keywords/type.js | 5 +- lib/keywords/unevaluatedItems.js | 7 +- lib/keywords/unevaluatedProperties.js | 17 +- lib/keywords/uniqueItems.js | 5 +- lib/keywords/unknown.js | 3 +- lib/keywords/validation.js | 8 +- lib/keywords/writeOnly.js | 3 +- lib/output.js | 2 +- openapi-3-0/discriminator.js | 3 +- openapi-3-0/example.js | 3 +- openapi-3-0/externalDocs.js | 3 +- openapi-3-0/json-schema-test-suite.spec.ts | 3 +- openapi-3-0/type.js | 5 +- openapi-3-0/xml.js | 3 +- openapi-3-1/json-schema-test-suite.spec.ts | 3 +- stable/json-schema-test-suite.spec.ts | 3 +- 76 files changed, 468 insertions(+), 380 deletions(-) create mode 100644 lib/instance.d.ts create mode 100644 lib/instance.js delete mode 100644 lib/json-node.d.ts delete mode 100644 lib/json-node.js diff --git a/annotations/index.d.ts b/annotations/index.d.ts index 8e7b19ef..71da0b4c 100644 --- a/annotations/index.d.ts +++ b/annotations/index.d.ts @@ -1,17 +1,22 @@ import type { OutputFormat, OutputUnit } from "../lib/index.js"; import type { CompiledSchema } from "../lib/experimental.js"; import type { JsonNode } from "../lib/json-node.js"; +import type { Json } from "@hyperjump/json-pointer"; export const annotate: ( - (schemaUrl: string, value: unknown, outputFormat?: OutputFormat) => Promise + (schemaUrl: string, value: Json, outputFormat?: OutputFormat) => Promise ) & ( (schemaUrl: string) => Promise ); +export type Annotator = (value: Json, outputFormat?: OutputFormat) => JsonNode; + export const interpret: (compiledSchema: CompiledSchema, value: JsonNode, outputFormat?: OutputFormat) => JsonNode; -export type Annotator = (value: unknown, outputFormat?: OutputFormat) => JsonNode; +export const setAnnotation: (keywordUri: string, schemaLocation: string, value: string) => void; +export const annotation: (instance: JsonNode, keyword: string, dialectUri?: string) => A[]; +export const annotatedWith: (instance: JsonNode, keyword: string, dialectUri?: string) => JsonNode[]; export class ValidationError extends Error { public output: OutputUnit; diff --git a/annotations/index.js b/annotations/index.js index 054a1cb9..e3b479ed 100644 --- a/annotations/index.js +++ b/annotations/index.js @@ -1,12 +1,13 @@ +import * as JsonPointer from "@hyperjump/json-pointer"; import { ValidationError } from "./validation-error.js"; -import { getSchema, compile, interpret as validate, BASIC } from "../lib/experimental.js"; -import { jsonNodeFromJs } from "../lib/json-node.js"; +import { getSchema, compile, interpret as validate, BASIC, getKeywordId } from "../lib/experimental.js"; +import * as Instance from "../lib/instance.js"; export const annotate = async (schemaUri, json = undefined, outputFormat = undefined) => { const schema = await getSchema(schemaUri); const compiled = await compile(schema); - const interpretAst = (json, outputFormat) => interpret(compiled, jsonNodeFromJs(json), outputFormat); + const interpretAst = (json, outputFormat) => interpret(compiled, Instance.fromJs(json), outputFormat); return json === undefined ? interpretAst : interpretAst(json, outputFormat); }; @@ -20,4 +21,44 @@ export const interpret = ({ ast, schemaUri }, instance, outputFormat = BASIC) => return instance; }; +export const setAnnotation = (node, keywordUri, schemaLocation, value) => { + if (!(keywordUri in node.annotations)) { + node.annotations[keywordUri] = {}; + } + node.annotations[keywordUri][schemaLocation] = value; +}; + +export const annotation = (node, keyword, dialect = "https://json-schema.org/validation") => { + const keywordUri = getKeywordId(keyword, dialect); + + let currentNode = node.root; + const errors = Object.keys(node.root.errors); + for (let segment of JsonPointer.pointerSegments(node.pointer)) { + segment = segment === "-" && currentNode.typeOf() === "array" ? currentNode.length() : segment; + currentNode = Instance.step(segment, currentNode); + errors.push(...Object.keys(currentNode.errors)); + } + + const annotations = []; + for (const schemaLocation in node.annotations[keywordUri]) { + if (!errors.some((error) => schemaLocation.startsWith(error))) { + annotations.unshift(node.annotations[keywordUri][schemaLocation]); + } + } + + return annotations; +}; + +export const annotatedWith = (instance, keyword, dialectId = "https://json-schema.org/validation") => { + const nodes = []; + + for (const node of Instance.allNodes(instance)) { + if (annotation(node, keyword, dialectId).length > 0) { + nodes.push(node); + } + } + + return nodes; +}; + export { ValidationError } from "./validation-error.js"; diff --git a/annotations/index.spec.ts b/annotations/index.spec.ts index babfb1b1..b262d2e6 100644 --- a/annotations/index.spec.ts +++ b/annotations/index.spec.ts @@ -3,17 +3,19 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, it, expect, beforeEach, beforeAll, afterAll } from "vitest"; import { toAbsoluteIri } from "@hyperjump/uri"; -import { annotate } from "./index.js"; +import { annotate, annotation } from "./index.js"; import { registerSchema, unregisterSchema } from "../lib/index.js"; import "../stable/index.js"; import "../draft-2020-12/index.js"; import "../draft-07/index.js"; import "../draft-06/index.js"; import "../draft-04/index.js"; +import * as Instance from "../lib/instance.js"; import type { SchemaObject } from "../lib/index.js"; import type { Annotator } from "./index.js"; -import type { JsonNode } from "../lib/json-node.js"; +import type { JsonNode } from "../lib/instance.js"; +import type { Json } from "@hyperjump/json-pointer"; type Suite = { @@ -23,7 +25,7 @@ type Suite = { }; type Subject = { - instance: unknown; + instance: Json; assertions: Assertion[]; }; @@ -69,14 +71,14 @@ describe("Annotations", () => { let instance: JsonNode; beforeEach(() => { - instance = annotator(subject.instance); + instance = annotator(subject.instance); // eslint-disable-line @typescript-eslint/no-unsafe-assignment }); subject.assertions.forEach((assertion) => { it(`${assertion.keyword} annotations at '${assertion.location}' should be ${JSON.stringify(assertion.expected)}`, () => { const dialect: string | undefined = suite.schema.$schema ? toAbsoluteIri(suite.schema.$schema as string) : undefined; - const annotations = instance.get(assertion.location) - ?.annotation(assertion.keyword, dialect) ?? []; + const subject = Instance.get(assertion.location, instance); + const annotations = subject ? annotation(subject, assertion.keyword, dialect) : []; expect(annotations).to.eql(assertion.expected); }); }); diff --git a/bundle/generate-snapshots.js b/bundle/generate-snapshots.js index 17348a1b..18e29df2 100644 --- a/bundle/generate-snapshots.js +++ b/bundle/generate-snapshots.js @@ -7,7 +7,7 @@ import "../draft-2019-09/index.js"; import "../draft-07/index.js"; import "../draft-06/index.js"; import "../draft-04/index.js"; -import { jsonNodeFromJs } from "../lib/json-node.js"; +import * as Instance from "../lib/instance.js"; const suite = testSuite("./bundle/tests"); @@ -25,7 +25,7 @@ const snapshotGenerator = async (version, dialect) => { loadSchemas(testCase, mainSchemaUri, dialect); const schema = await getSchema(mainSchemaUri); const compiledSchema = await compile(schema); - const instance = jsonNodeFromJs(test.instance); + const instance = Instance.fromJs(test.instance); interpret(compiledSchema, instance); const expectedOutput = toOutput(instance); unloadSchemas(testCase, mainSchemaUri); diff --git a/bundle/test-suite.spec.ts b/bundle/test-suite.spec.ts index 19409b5f..3ae2f6d0 100644 --- a/bundle/test-suite.spec.ts +++ b/bundle/test-suite.spec.ts @@ -3,7 +3,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { isCompatible, md5, loadSchemas, unloadSchemas, toOutput, testSuite } from "./test-utils.js"; import { registerSchema, unregisterSchema } from "../lib/index.js"; import { compile, getKeywordName, getSchema, interpret } from "../lib/experimental.js"; -import { jsonNodeFromJs } from "../lib/json-node.js"; +import * as Instance from "../lib/instance.js"; import "../stable/index.js"; import "../draft-2020-12/index.js"; import "../draft-2019-09/index.js"; @@ -58,7 +58,7 @@ const testRunner = (version: number, dialect: string) => { it(test.description, async () => { const schema = await getSchema(mainSchemaUri); const compiledSchema = await compile(schema); - const instance = jsonNodeFromJs(test.instance); + const instance = Instance.fromJs(test.instance); interpret(compiledSchema, instance); const output = toOutput(instance); diff --git a/bundle/test-utils.js b/bundle/test-utils.js index 6687c368..fd0eb2a5 100644 --- a/bundle/test-utils.js +++ b/bundle/test-utils.js @@ -3,7 +3,7 @@ import { readFileSync, readdirSync } from "node:fs"; import { basename, relative } from "node:path"; import { getKeywordName } from "../lib/keywords.js"; import { registerSchema, unregisterSchema } from "../lib/index.js"; -import { allNodes } from "../lib/json-node.js"; +import * as Instance from "../lib/instance.js"; export const testSuite = (path) => { @@ -105,8 +105,8 @@ export const unloadSchemas = (testCase, retrievalUri) => { export const toOutput = (root) => { const output = {}; - for (const node of allNodes(root)) { - output[node.uri()] = { + for (const node of Instance.allNodes(root)) { + output[Instance.uri(node)] = { errors: node.errors, annotations: node.annotations }; diff --git a/draft-04/additionalItems.js b/draft-04/additionalItems.js index 15a9b22c..e852ef0c 100644 --- a/draft-04/additionalItems.js +++ b/draft-04/additionalItems.js @@ -1,5 +1,6 @@ import { pipe, drop, every } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../lib/instance.js"; import { getKeywordName, Validation } from "../lib/experimental.js"; @@ -14,12 +15,12 @@ const compile = async (schema, ast, parentSchema) => { }; const interpret = ([numberOfItems, additionalItems], instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "array") { + if (Instance.typeOf(instance) !== "array") { return true; } return pipe( - instance.iter(), + Instance.iter(instance), drop(numberOfItems), every((item) => Validation.interpret(additionalItems, item, ast, dynamicAnchors, quiet)) ); @@ -31,7 +32,7 @@ const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { } const evaluatedIndexes = new Set(); - for (let ndx = keywordValue[0]; ndx < instance.length(); ndx++) { + for (let ndx = keywordValue[0]; ndx < Instance.length(instance); ndx++) { evaluatedIndexes.add(ndx); } diff --git a/draft-04/dependencies.js b/draft-04/dependencies.js index b1d2b1d8..73f8c4d8 100644 --- a/draft-04/dependencies.js +++ b/draft-04/dependencies.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../lib/instance.js"; import { Validation } from "../lib/experimental.js"; @@ -15,9 +16,9 @@ const compile = (schema, ast) => pipe( ); const interpret = (dependencies, instance, ast, dynamicAnchors, quiet) => { - const value = instance.value(); + const value = Instance.value(instance); - return instance.typeOf() !== "object" || dependencies.every(([propertyName, dependency]) => { + return Instance.typeOf(instance) !== "object" || dependencies.every(([propertyName, dependency]) => { if (!(propertyName in value)) { return true; } diff --git a/draft-04/items.js b/draft-04/items.js index 103e1510..5d9e366d 100644 --- a/draft-04/items.js +++ b/draft-04/items.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectArray, every, zip, take, range, collectSet } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../lib/instance.js"; import { Validation } from "../lib/experimental.js"; @@ -18,16 +19,16 @@ const compile = (schema, ast) => { }; const interpret = (items, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "array") { + if (Instance.typeOf(instance) !== "array") { return true; } if (typeof items === "string") { - return every((itemValue) => Validation.interpret(items, itemValue, ast, dynamicAnchors, quiet), instance.iter()); + return every((itemValue) => Validation.interpret(items, itemValue, ast, dynamicAnchors, quiet), Instance.iter(instance)); } else { return pipe( - zip(items, instance.iter()), - take(instance.length()), + zip(items, Instance.iter(instance)), + take(Instance.length(instance)), every(([prefixItem, item]) => Validation.interpret(prefixItem, item, ast, dynamicAnchors, quiet)) ); } @@ -35,7 +36,7 @@ const interpret = (items, instance, ast, dynamicAnchors, quiet) => { const collectEvaluatedItems = (items, instance, ast, dynamicAnchors) => { return interpret(items, instance, ast, dynamicAnchors, true) && (typeof items === "string" - ? collectSet(range(0, instance.length())) + ? collectSet(range(0, Instance.length(instance))) : collectSet(range(0, items.length))); }; diff --git a/draft-04/json-schema-test-suite.spec.ts b/draft-04/json-schema-test-suite.spec.ts index 6fc12fec..9e51e3e0 100644 --- a/draft-04/json-schema-test-suite.spec.ts +++ b/draft-04/json-schema-test-suite.spec.ts @@ -3,6 +3,7 @@ import { describe, it, beforeAll, expect, afterAll } from "vitest"; import { toAbsoluteIri } from "@hyperjump/uri"; import { registerSchema, unregisterSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { JsonSchemaDraft04, SchemaObject, Validator } from "./index.js"; @@ -14,7 +15,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/draft-04/maximum.js b/draft-04/maximum.js index 81c97ea2..a2a889ce 100644 --- a/draft-04/maximum.js +++ b/draft-04/maximum.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../lib/instance.js"; import { getKeywordName } from "../lib/experimental.js"; @@ -13,11 +14,11 @@ const compile = async (schema, _ast, parentSchema) => { }; const interpret = ([maximum, isExclusive], instance) => { - if (instance.typeOf() !== "number") { + if (Instance.typeOf(instance) !== "number") { return true; } - const value = instance.value(); + const value = Instance.value(instance); return isExclusive ? value < maximum : value <= maximum; }; diff --git a/draft-04/minimum.js b/draft-04/minimum.js index 61c5ba00..c0146aba 100644 --- a/draft-04/minimum.js +++ b/draft-04/minimum.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../lib/instance.js"; import { getKeywordName } from "../lib/experimental.js"; @@ -13,11 +14,11 @@ const compile = async (schema, _ast, parentSchema) => { }; const interpret = ([minimum, isExclusive], instance) => { - if (instance.typeOf() !== "number") { + if (Instance.typeOf(instance) !== "number") { return true; } - const value = instance.value(); + const value = Instance.value(instance); return isExclusive ? value > minimum : value >= minimum; }; diff --git a/draft-06/contains.js b/draft-06/contains.js index fc996e7f..bb185808 100644 --- a/draft-06/contains.js +++ b/draft-06/contains.js @@ -1,4 +1,5 @@ import { some } from "@hyperjump/pact"; +import * as Instance from "../lib/instance.js"; import { Validation } from "../lib/experimental.js"; @@ -7,7 +8,7 @@ const id = "https://json-schema.org/keyword/draft-06/contains"; const compile = (schema, ast) => Validation.compile(schema, ast); const interpret = (contains, instance, ast, dynamicAnchors, quiet) => { - return instance.typeOf() !== "array" || some((item) => Validation.interpret(contains, item, ast, dynamicAnchors, quiet), instance.iter()); + return Instance.typeOf(instance) !== "array" || some((item) => Validation.interpret(contains, item, ast, dynamicAnchors, quiet), Instance.iter(instance)); }; export default { id, compile, interpret }; diff --git a/draft-06/json-schema-test-suite.spec.ts b/draft-06/json-schema-test-suite.spec.ts index 776345b1..9e341fb3 100644 --- a/draft-06/json-schema-test-suite.spec.ts +++ b/draft-06/json-schema-test-suite.spec.ts @@ -4,6 +4,7 @@ import { toAbsoluteIri } from "@hyperjump/uri"; import { registerSchema, unregisterSchema, validate } from "./index.js"; import type { JsonSchemaDraft06, SchemaObject, Validator } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; type Suite = { @@ -14,7 +15,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/draft-07/json-schema-test-suite.spec.ts b/draft-07/json-schema-test-suite.spec.ts index f18ac092..2feab26c 100644 --- a/draft-07/json-schema-test-suite.spec.ts +++ b/draft-07/json-schema-test-suite.spec.ts @@ -3,6 +3,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { toAbsoluteIri } from "@hyperjump/uri"; import { registerSchema, unregisterSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { JsonSchemaDraft07, SchemaObject, Validator } from "./index.js"; @@ -14,7 +15,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/draft-2019-09/json-schema-test-suite.spec.ts b/draft-2019-09/json-schema-test-suite.spec.ts index 921a6e6f..a768459c 100644 --- a/draft-2019-09/json-schema-test-suite.spec.ts +++ b/draft-2019-09/json-schema-test-suite.spec.ts @@ -3,6 +3,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { toAbsoluteIri } from "@hyperjump/uri"; import { registerSchema, unregisterSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { JsonSchemaDraft201909, SchemaObject, Validator } from "./index.js"; @@ -14,7 +15,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/draft-2020-12/json-schema-test-suite.spec.ts b/draft-2020-12/json-schema-test-suite.spec.ts index 4be89190..3e988229 100644 --- a/draft-2020-12/json-schema-test-suite.spec.ts +++ b/draft-2020-12/json-schema-test-suite.spec.ts @@ -3,6 +3,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { toAbsoluteIri } from "@hyperjump/uri"; import { registerSchema, unregisterSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { JsonSchemaDraft202012, SchemaObject, Validator } from "./index.js"; @@ -14,7 +15,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/lib/core.js b/lib/core.js index b339e889..a81e9726 100644 --- a/lib/core.js +++ b/lib/core.js @@ -6,7 +6,7 @@ import { getShouldValidateSchema, getMetaSchemaOutputFormat } from "./configuration.js"; -import { jsonNodeFromJs } from "./json-node.js"; +import * as Instance from "./instance.js"; import { InvalidSchemaError } from "./invalid-schema-error.js"; import { getSchema, registerSchema, unregisterSchema as schemaUnregister } from "./schema.js"; import { getKeywordName } from "./keywords.js"; @@ -20,7 +20,7 @@ setMetaSchemaOutputFormat(FLAG); export const validate = async (url, value = undefined, outputFormat = undefined) => { const schema = await getSchema(url); const compiled = await compile(schema); - const interpretAst = (value, outputFormat) => interpret(compiled, jsonNodeFromJs(value), outputFormat); + const interpretAst = (value, outputFormat) => interpret(compiled, Instance.fromJs(value), outputFormat); return value === undefined ? interpretAst : interpretAst(value, outputFormat); }; @@ -49,7 +49,7 @@ subscribe("validate.metaValidate", async (_message, schema) => { } // Interpret - const schemaInstance = jsonNodeFromJs(schema.document.root, schema.document.baseUri); + const schemaInstance = Instance.fromJs(schema.document.root, schema.document.baseUri); const metaResults = metaValidators[schema.document.dialectId](schemaInstance, getMetaSchemaOutputFormat()); if (!metaResults.valid) { throw new InvalidSchemaError(metaResults); diff --git a/lib/index.d.ts b/lib/index.d.ts index f11b3672..f3192711 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,3 +1,6 @@ +import type { Json } from "@hyperjump/json-pointer"; + + export type SchemaFragment = string | number | boolean | null | SchemaObject | SchemaFragment[]; export type SchemaObject = { [keyword: string]: SchemaFragment; @@ -12,12 +15,12 @@ export const unregisterSchema: (retrievalUri: string) => void; export const addSchema: typeof registerSchema; export const validate: ( - (url: string, value: unknown, outputFormat?: OutputFormat) => Promise + (url: string, value: Json, outputFormat?: OutputFormat) => Promise ) & ( (url: string) => Promise ); -export type Validator = (value: unknown, outputFormat?: OutputFormat) => OutputUnit; +export type Validator = (value: Json, outputFormat?: OutputFormat) => OutputUnit; export type OutputUnit = { keyword: string; diff --git a/lib/instance.d.ts b/lib/instance.d.ts new file mode 100644 index 00000000..fec95402 --- /dev/null +++ b/lib/instance.d.ts @@ -0,0 +1,30 @@ +import type { Json } from "@hyperjump/json-pointer"; + + +export const fromJs: (value: Json, uri?: string) => JsonNode; + +export const get: (url: string, context: JsonNode) => JsonNode | undefined; +export const uri: (node: JsonNode) => string; +export const value: (node: JsonNode) => A; +export const has: (key: string, node: JsonNode) => boolean; +export const typeOf: (node: JsonNode) => JsonType; +export const step: (key: string, node: JsonNode) => JsonNode; +export const iter: (node: JsonNode) => Generator; +export const keys: (node: JsonNode) => Generator; +export const values: (node: JsonNode) => Generator; +export const entries: (node: JsonNode) => Generator<[JsonNode, JsonNode]>; +export const length: (node: JsonNode) => number; + +export const allNodes: (node) => Generator; + +export type JsonNode = { + baseUri: string; + pointer: string; + type: JsonNodeType; + children: JsonNode[]; + parent: JsonNode; + root: JsonNode; + valid: boolean; +}; + +type JsonNodeType = "object" | "array" | "string" | "number" | "boolean" | "null" | "property"; diff --git a/lib/instance.js b/lib/instance.js new file mode 100644 index 00000000..342f19e3 --- /dev/null +++ b/lib/instance.js @@ -0,0 +1,158 @@ +import * as JsonPointer from "@hyperjump/json-pointer"; +import { reduce } from "@hyperjump/pact"; +import { toAbsoluteIri } from "@hyperjump/uri"; +import { Reference } from "@hyperjump/browser/jref"; +import { toAbsoluteUri, uriFragment } from "./common.js"; + + +export const fromJs = (value, uri = "", pointer = "", parent = undefined) => { + const jsType = typeof value; + + switch (jsType) { + case "number": + case "string": + case "boolean": + return cons(uri, pointer, value, jsType, [], parent); + case "object": + if (value === null) { + return cons(uri, pointer, value, "null", [], parent); + } else if (Array.isArray(value)) { + const arrayNode = cons(uri, pointer, value, "array", [], parent); + arrayNode.children = value.map((item, index) => { + return fromJs(item, uri, JsonPointer.append(index, pointer), arrayNode); + }); + return arrayNode; + } else if (Object.getPrototypeOf(value) === Object.prototype) { + const objectNode = cons(uri, pointer, value, "object", [], parent); + objectNode.children = Object.entries(value).map((entry) => { + const propertyPointer = JsonPointer.append(entry[0], pointer); + const propertyNode = cons(uri, propertyPointer, undefined, "property", [], objectNode); + propertyNode.children = entry.map((property) => fromJs(property, uri, propertyPointer, propertyNode)); + return propertyNode; + }); + return objectNode; + } else if (value instanceof Reference) { + return fromJs(value.toJSON(), uri, pointer, parent); + } + default: + const type = jsType === "object" ? Object.getPrototypeOf(value).constructor.name || "anonymous" : jsType; + throw Error(`Not a JSON compatible type: ${type}`); + } +}; + +const cons = (baseUri, pointer, value, type, children, parent) => { + const node = { + baseUri: baseUri ? toAbsoluteIri(baseUri) : "", + pointer: pointer, + value: value, + type: type, + children: children, + parent: parent, + valid: true, + errors: {}, + annotations: {} + }; + node.root = parent?.root ?? node; + + return node; +}; + +export const get = (uri, instance) => { + const schemaId = toAbsoluteUri(uri); + if (schemaId !== instance.baseUri && schemaId !== "") { + throw Error(`Reference '${uri}' is not local to '${instance.baseUri}'`); + } + + const pointer = uriFragment(uri); + return reduce((node, segment) => { + segment = segment === "-" && typeOf(node) === "array" ? length(node) : segment; + return step(segment, node); + }, instance.root, JsonPointer.pointerSegments(pointer)); +}; + +export const uri = (node) => `${node.baseUri}#${encodeURI(node.pointer)}`; +export const value = (node) => node.value; +export const typeOf = (node) => node.type; +export const has = (node, key) => key in node.value; + +export const step = (key, node) => { + if (node.type !== "object" && node.type !== "array") { + return; + } + + switch (node.type) { + case "object": + const property = node.children.find((propertyNode) => { + return value(propertyNode.children[0]) === key; + }); + return property?.children[1]; + case "array": + const index = parseInt(key, 10); + return node.children[index]; + default: + return; + } +}; + +export const iter = function* (node) { + if (node.type !== "array") { + return; + } + + yield* node.children; +}; + +export const keys = function* (node) { + if (node.type !== "object") { + return; + } + + for (const property of node.children) { + yield property.children[0]; + } +}; + +export const values = function* (node) { + if (node.type !== "object") { + return; + } + + for (const property of node.children) { + yield property.children[1]; + } +}; + +export const entries = function* (node) { + if (node.type !== "object") { + return; + } + + for (const property of node.children) { + yield property.children; + } +}; + +export const length = (node) => { + if (node.type !== "array") { + return; + } + + return node.children.length; +}; + +export const allNodes = function* (node) { + yield node; + + switch (typeOf(node)) { + case "object": + for (const child of values(node)) { + yield* allNodes(child); + } + break; + case "array": + for (const child of iter(node)) { + yield* allNodes(child); + } + break; + } +}; diff --git a/lib/json-node.d.ts b/lib/json-node.d.ts deleted file mode 100644 index d8e2d1f3..00000000 --- a/lib/json-node.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -type JsonNodeType = "object" | "array" | "string" | "number" | "boolean" | "null" | "undefined" | "property"; - -export class JsonNode { - constructor(baseUri: string, pointer: string, root: JsonNode, value: unknown, type: JsonNodeType, children: JsonNode[]); - get(uri: string): JsonNode?; - uri(): string; - value(): A; - typeOf(): JsonNodeType; - has(key: string): boolean; - step(key: string): JsonNode?; - * entries(): Generator<[JsonNode, JsonNode]>; - * keys(): Generator; - * values(): Generator; - * iter(): Generator; - length(): number; - valid: boolean; - error(schemaLocation: string, keywordUri: string): void; - annotate(keywordUri: string, schemaLocation: string, value: string): void; - annotation(keyword: string, dialect?: string): unknown[]; - annotatedWith(keyword: string, dialect?: string): JsonNode[]; -} - -export const jsonNodeFromJs: (value: unknown, uri?: string) => JsonNode; diff --git a/lib/json-node.js b/lib/json-node.js deleted file mode 100644 index 51fd350f..00000000 --- a/lib/json-node.js +++ /dev/null @@ -1,212 +0,0 @@ -import * as JsonPointer from "@hyperjump/json-pointer"; -import { reduce } from "@hyperjump/pact"; -import { toAbsoluteIri } from "@hyperjump/uri"; -import { Reference } from "@hyperjump/browser/jref"; -import { toAbsoluteUri, uriFragment } from "./common.js"; -import { getKeywordId } from "./keywords.js"; - - -export class JsonNode { - constructor(baseUri, pointer, value, type, children, parent) { - this.baseUri = baseUri ? toAbsoluteIri(baseUri) : ""; - this.pointer = pointer; - this.root = parent?.root ?? this; - this._value = value; - this.type = type; - this.children = children; - this.parent = parent; - this.valid = true; - this.errors = {}; - this.annotations = {}; - } - - get(uri) { - const schemaId = toAbsoluteUri(uri); - if (schemaId !== this.baseUri && schemaId !== "") { - throw Error(`Reference '${uri}' is not local to '${this.baseUri}'`); - } - - const pointer = uriFragment(uri); - return reduce((node, segment) => { - segment = segment === "-" && node?.typeOf() === "array" ? node.length() : segment; - return node?.step(segment); - }, this.root, JsonPointer.pointerSegments(pointer)); - } - - uri() { - return `${this.baseUri}#${this.pointer}`; - } - - value() { - return this._value; - } - - typeOf() { - return this.type; - } - - has(key) { - return key in this._value; - } - - step(key) { - if (this.type !== "object" && this.type !== "array") { - return; - } - - switch (this.type) { - case "object": - const property = this.children.find((propertyNode) => { - return propertyNode.children[0].value() === key; - }); - return property?.children[1]; - case "array": - const index = parseInt(key, 10); - return this.children[index]; - default: - return; - } - } - - * entries() { - if (this.type !== "object") { - return; - } - - for (const property of this.children) { - yield property.children; - } - } - - * keys() { - if (this.type !== "object") { - return; - } - - for (const property of this.children) { - yield property.children[0]; - } - } - - * values() { - if (this.type !== "object") { - return; - } - - for (const property of this.children) { - yield property.children[1]; - } - } - - * iter() { - if (this.type !== "array") { - return; - } - - yield* this.children; - } - - length() { - if (this.type !== "array") { - return; - } - - return this.children.length; - } - - error(schemaLocation, keywordUri) { - this.errors[schemaLocation] = keywordUri; - } - - annotate(keywordUri, schemaLocation, value) { - if (!(keywordUri in this.annotations)) { - this.annotations[keywordUri] = {}; - } - this.annotations[keywordUri][schemaLocation] = value; - } - - annotation(keyword, dialect = "https://json-schema.org/validation") { - const keywordUri = getKeywordId(keyword, dialect); - - let currentNode = this.root; - const errors = Object.keys(this.root.errors); - for (let segment of JsonPointer.pointerSegments(this.pointer)) { - segment = segment === "-" && currentNode.typeOf() === "array" ? currentNode.length() : segment; - currentNode = currentNode.step(segment); - errors.push(...Object.keys(currentNode.errors)); - } - - const annotations = []; - for (const schemaLocation in this.annotations[keywordUri]) { - if (!errors.some((error) => schemaLocation.startsWith(error))) { - annotations.unshift(this.annotations[keywordUri][schemaLocation]); - } - } - - return annotations; - } - - annotatedWith(keyword, dialectId = "https://json-schema.org/validation") { - const nodes = []; - - for (const node of allNodes(this)) { - if (node.annotation(keyword, dialectId).length > 0) { - nodes.push(node); - } - } - - return nodes; - } -} - -export const jsonNodeFromJs = (value, uri = "", pointer = "", parent = undefined) => { - const jsType = typeof value; - - switch (jsType) { - case "number": - case "string": - case "boolean": - return new JsonNode(uri, pointer, value, jsType, [], parent); - case "object": - if (value === null) { - return new JsonNode(uri, pointer, value, "null", [], parent); - } else if (Array.isArray(value)) { - const arrayNode = new JsonNode(uri, pointer, value, "array", [], parent); - arrayNode.children = value.map((item, index) => { - return jsonNodeFromJs(item, uri, JsonPointer.append(index, pointer), arrayNode); - }); - return arrayNode; - } else if (Object.getPrototypeOf(value) === Object.prototype) { - const objectNode = new JsonNode(uri, pointer, value, "object", [], parent); - objectNode.children = Object.entries(value).map((entry) => { - const propertyPointer = JsonPointer.append(entry[0], pointer); - const propertyNode = new JsonNode(uri, propertyPointer, undefined, "property", [], objectNode); - propertyNode.children = entry.map((property) => jsonNodeFromJs(property, uri, propertyPointer, propertyNode)); - return propertyNode; - }); - return objectNode; - } else if (value instanceof Reference) { - return jsonNodeFromJs(value.toJSON(), uri, pointer, parent); - } - default: - const type = jsType === "object" ? Object.getPrototypeOf(value).constructor.name || "anonymous" : jsType; - throw Error(`Not a JSON compatible type: ${type}`); - } -}; - -export const allNodes = function* (node) { - yield node; - - switch (node.typeOf()) { - case "object": - for (const child of node.values()) { - yield* allNodes(child); - } - break; - case "array": - for (const child of node.iter()) { - yield* allNodes(child); - } - break; - } -}; diff --git a/lib/keywords/additionalProperties.js b/lib/keywords/additionalProperties.js index 0364cc93..f42a6096 100644 --- a/lib/keywords/additionalProperties.js +++ b/lib/keywords/additionalProperties.js @@ -1,5 +1,6 @@ import { concat, join, empty, map, pipe } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { getKeywordName, Validation } from "../experimental.js"; @@ -31,13 +32,14 @@ const regexEscape = (string) => string .replace(/-/g, "\\x2d"); const interpret = ([isDefinedProperty, additionalProperties], instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; - for (const [propertyName, property] of instance.entries()) { - if (!isDefinedProperty.test(propertyName.value()) && !Validation.interpret(additionalProperties, property, ast, dynamicAnchors, quiet)) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (!isDefinedProperty.test(propertyName) && !Validation.interpret(additionalProperties, property, ast, dynamicAnchors, quiet)) { isValid = false; } } @@ -46,18 +48,19 @@ const interpret = ([isDefinedProperty, additionalProperties], instance, ast, dyn }; const collectEvaluatedProperties = ([isDefinedProperty, additionalProperties], instance, ast, dynamicAnchors) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } const evaluatedPropertyNames = new Set(); - for (const [propertyName, property] of instance.entries()) { - if (!isDefinedProperty.test(propertyName.value())) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (!isDefinedProperty.test(propertyName)) { if (!Validation.interpret(additionalProperties, property, ast, dynamicAnchors, true)) { return false; } - evaluatedPropertyNames.add(propertyName.value()); + evaluatedPropertyNames.add(propertyName); } } diff --git a/lib/keywords/const.js b/lib/keywords/const.js index 01890f2b..ef9a21e1 100644 --- a/lib/keywords/const.js +++ b/lib/keywords/const.js @@ -1,10 +1,11 @@ import jsonStringify from "json-stringify-deterministic"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/const"; const compile = (schema) => jsonStringify(Browser.value(schema)); -const interpret = (const_, instance) => jsonStringify(instance.value()) === const_; +const interpret = (const_, instance) => jsonStringify(Instance.value(instance)) === const_; export default { id, compile, interpret }; diff --git a/lib/keywords/contains.js b/lib/keywords/contains.js index 173800c3..a317f5c1 100644 --- a/lib/keywords/contains.js +++ b/lib/keywords/contains.js @@ -1,5 +1,6 @@ import { pipe, filter, reduce, zip, range, map, collectSet } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { getKeywordName, Validation } from "../experimental.js"; @@ -20,8 +21,8 @@ const compile = async (schema, ast, parentSchema) => { }; const interpret = ({ contains, minContains, maxContains }, instance, ast, dynamicAnchors, quiet) => { - const matches = instance.typeOf() !== "array" || pipe( - instance.iter(), + const matches = Instance.typeOf(instance) !== "array" || pipe( + Instance.iter(instance), filter((item) => Validation.interpret(contains, item, ast, dynamicAnchors, quiet)), reduce((matches) => matches + 1, 0) ); @@ -30,9 +31,9 @@ const interpret = ({ contains, minContains, maxContains }, instance, ast, dynami const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { return interpret(keywordValue, instance, ast, dynamicAnchors, true) - && instance.typeOf() === "array" + && Instance.typeOf(instance) === "array" && pipe( - zip(instance.iter(), range(0)), + zip(Instance.iter(instance), range(0)), filter(([item]) => Validation.interpret(keywordValue.contains, item, ast, dynamicAnchors, true)), map(([, itemIndex]) => itemIndex), collectSet diff --git a/lib/keywords/contentEncoding.js b/lib/keywords/contentEncoding.js index 906c5318..3ba88370 100644 --- a/lib/keywords/contentEncoding.js +++ b/lib/keywords/contentEncoding.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/contentEncoding"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/contentEncoding"; const compile = (schema) => Browser.value(schema); const interpret = (contentEncoding, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, contentEncoding); + setAnnotation(instance, id, schemaLocation, contentEncoding); return true; }; diff --git a/lib/keywords/contentMediaType.js b/lib/keywords/contentMediaType.js index ec3de962..6c41fe72 100644 --- a/lib/keywords/contentMediaType.js +++ b/lib/keywords/contentMediaType.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/contentMediaType"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/contentMediaType"; const compile = (schema) => Browser.value(schema); const interpret = (contentMediaType, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, contentMediaType); + setAnnotation(instance, id, schemaLocation, contentMediaType); return true; }; diff --git a/lib/keywords/contentSchema.js b/lib/keywords/contentSchema.js index 4928ea7a..999daf49 100644 --- a/lib/keywords/contentSchema.js +++ b/lib/keywords/contentSchema.js @@ -1,4 +1,5 @@ import { canonicalUri } from "../schema.js"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/contentSchema"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/contentSchema"; const compile = (contentSchema) => canonicalUri(contentSchema); const interpret = (contentSchema, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, contentSchema); + setAnnotation(instance, id, schemaLocation, contentSchema); return true; }; diff --git a/lib/keywords/default.js b/lib/keywords/default.js index 36e0bf4f..4d306e3f 100644 --- a/lib/keywords/default.js +++ b/lib/keywords/default.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/default"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/default"; const compile = (schema) => Browser.value(schema); const interpret = (value, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, value); + setAnnotation(instance, id, schemaLocation, value); return true; }; diff --git a/lib/keywords/dependentRequired.js b/lib/keywords/dependentRequired.js index 31d442bc..1ce35b73 100644 --- a/lib/keywords/dependentRequired.js +++ b/lib/keywords/dependentRequired.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/dependentRequired"; @@ -11,13 +12,13 @@ const compile = (schema) => pipe( ); const interpret = (dependentRequired, instance) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; for (const [propertyName, required] of dependentRequired) { - if (instance.has(propertyName) && !required.every((key) => instance.has(key))) { + if (Instance.has(instance, propertyName) && !required.every((key) => Instance.has(instance, key))) { isValid = false; } } diff --git a/lib/keywords/dependentSchemas.js b/lib/keywords/dependentSchemas.js index 9ac1f74a..fbe10ee0 100644 --- a/lib/keywords/dependentSchemas.js +++ b/lib/keywords/dependentSchemas.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { Validation } from "../experimental.js"; @@ -12,13 +13,13 @@ const compile = (schema, ast) => pipe( ); const interpret = (dependentSchemas, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; for (const [propertyName, dependentSchema] of dependentSchemas) { - if (instance.has(propertyName) && !Validation.interpret(dependentSchema, instance, ast, dynamicAnchors, quiet)) { + if (Instance.has(instance, propertyName) && !Validation.interpret(dependentSchema, instance, ast, dynamicAnchors, quiet)) { isValid = false; } } @@ -27,13 +28,13 @@ const interpret = (dependentSchemas, instance, ast, dynamicAnchors, quiet) => { }; const collectEvaluatedProperties = (dependentSchemas, instance, ast, dynamicAnchors) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return false; } const evaluatedPropertyNames = new Set(); for (const [propertyName, dependentSchema] of dependentSchemas) { - if (instance.has(propertyName)) { + if (Instance.has(instance, propertyName)) { const propertyNames = Validation.collectEvaluatedProperties(dependentSchema, instance, ast, dynamicAnchors); if (propertyNames === false) { return false; diff --git a/lib/keywords/deprecated.js b/lib/keywords/deprecated.js index 94c4aa8a..289b0dde 100644 --- a/lib/keywords/deprecated.js +++ b/lib/keywords/deprecated.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/deprecated"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/deprecated"; const compile = (schema) => Browser.value(schema); const interpret = (deprecated, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, deprecated); + setAnnotation(instance, id, schemaLocation, deprecated); return true; }; diff --git a/lib/keywords/description.js b/lib/keywords/description.js index 74596747..c2e6c121 100644 --- a/lib/keywords/description.js +++ b/lib/keywords/description.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/description"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/description"; const compile = (schema) => Browser.value(schema); const interpret = (description, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, description); + setAnnotation(instance, id, schemaLocation, description); return true; }; diff --git a/lib/keywords/enum.js b/lib/keywords/enum.js index 3d480953..8c32e9fc 100644 --- a/lib/keywords/enum.js +++ b/lib/keywords/enum.js @@ -1,6 +1,7 @@ import jsonStringify from "json-stringify-deterministic"; import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/enum"; @@ -13,7 +14,7 @@ const compile = (schema) => pipe( ); const interpret = (enum_, instance) => { - const instanceValue = jsonStringify(instance.value()); + const instanceValue = jsonStringify(Instance.value(instance)); return enum_.some((enumValue) => instanceValue === enumValue); }; diff --git a/lib/keywords/examples.js b/lib/keywords/examples.js index ab1de2a3..1e34387e 100644 --- a/lib/keywords/examples.js +++ b/lib/keywords/examples.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/examples"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/examples"; const compile = (schema) => Browser.value(schema); const interpret = (examples, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, examples); + setAnnotation(instance, id, schemaLocation, examples); return true; }; diff --git a/lib/keywords/exclusiveMaximum.js b/lib/keywords/exclusiveMaximum.js index 58273cb9..c893e7ee 100644 --- a/lib/keywords/exclusiveMaximum.js +++ b/lib/keywords/exclusiveMaximum.js @@ -1,9 +1,10 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/exclusiveMaximum"; const compile = (schema) => Browser.value(schema); -const interpret = (exclusiveMaximum, instance) => instance.typeOf() !== "number" || instance.value() < exclusiveMaximum; +const interpret = (exclusiveMaximum, instance) => Instance.typeOf(instance) !== "number" || Instance.value(instance) < exclusiveMaximum; export default { id, compile, interpret }; diff --git a/lib/keywords/exclusiveMinimum.js b/lib/keywords/exclusiveMinimum.js index 5cff209c..403b3957 100644 --- a/lib/keywords/exclusiveMinimum.js +++ b/lib/keywords/exclusiveMinimum.js @@ -1,9 +1,10 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/exclusiveMinimum"; const compile = (schema) => Browser.value(schema); -const interpret = (exclusiveMinimum, instance) => instance.typeOf() !== "number" || instance.value() > exclusiveMinimum; +const interpret = (exclusiveMinimum, instance) => Instance.typeOf(instance) !== "number" || Instance.value(instance) > exclusiveMinimum; export default { id, compile, interpret }; diff --git a/lib/keywords/format.js b/lib/keywords/format.js index 25193a7e..846d4492 100644 --- a/lib/keywords/format.js +++ b/lib/keywords/format.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/format"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/format"; const compile = (schema) => Browser.value(schema); const interpret = (format, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, format); + setAnnotation(instance, id, schemaLocation, format); return true; }; diff --git a/lib/keywords/itemPattern.js b/lib/keywords/itemPattern.js index 87a039e5..65b417de 100644 --- a/lib/keywords/itemPattern.js +++ b/lib/keywords/itemPattern.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { Validation } from "../experimental.js"; import { fromEpsilon, fromSchema, closure, zeroOrOne, oneOrMore, concat, union } from "../nfa.js"; @@ -39,14 +40,14 @@ const compile = async (schema, ast) => { }; const evaluate = (strategy) => (nfa, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "array") { + if (Instance.typeOf(instance) !== "array") { return true; } let currentStates = []; addNextState(nfa.start, currentStates, []); - for (const item of instance.iter()) { + for (const item of Instance.iter(instance)) { const nextStates = []; for (const state of currentStates) { diff --git a/lib/keywords/items.js b/lib/keywords/items.js index f74b4fa2..cd6b3ddc 100644 --- a/lib/keywords/items.js +++ b/lib/keywords/items.js @@ -1,5 +1,6 @@ import { drop } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { getKeywordName, Validation } from "../experimental.js"; @@ -14,12 +15,12 @@ const compile = async (schema, ast, parentSchema) => { }; const interpret = ([numberOfPrefixItems, items], instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "array") { + if (Instance.typeOf(instance) !== "array") { return true; } let isValid = true; - for (const item of drop(numberOfPrefixItems, instance.iter())) { + for (const item of drop(numberOfPrefixItems, Instance.iter(instance))) { if (!Validation.interpret(items, item, ast, dynamicAnchors, quiet)) { isValid = false; } @@ -34,7 +35,7 @@ const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { } const evaluatedIndexes = new Set(); - for (let ndx = keywordValue[0]; ndx < instance.length(); ndx++) { + for (let ndx = keywordValue[0]; ndx < Instance.length(instance); ndx++) { evaluatedIndexes.add(ndx); } diff --git a/lib/keywords/maxItems.js b/lib/keywords/maxItems.js index e8a0478d..04f2a933 100644 --- a/lib/keywords/maxItems.js +++ b/lib/keywords/maxItems.js @@ -1,9 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/maxItems"; const compile = (schema) => Browser.value(schema); -const interpret = (maxItems, instance) => instance.typeOf() !== "array" || instance.length() <= maxItems; +const interpret = (maxItems, instance) => { + return Instance.typeOf(instance) !== "array" || Instance.length(instance) <= maxItems; +}; export default { id, compile, interpret }; diff --git a/lib/keywords/maxLength.js b/lib/keywords/maxLength.js index a483bafc..4dd8c3da 100644 --- a/lib/keywords/maxLength.js +++ b/lib/keywords/maxLength.js @@ -1,9 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/maxLength"; const compile = (schema) => Browser.value(schema); -const interpret = (maxLength, instance) => instance.typeOf() !== "string" || [...instance.value()].length <= maxLength; +const interpret = (maxLength, instance) => { + return Instance.typeOf(instance) !== "string" || [...Instance.value(instance)].length <= maxLength; +}; export default { id, compile, interpret }; diff --git a/lib/keywords/maxProperties.js b/lib/keywords/maxProperties.js index 5b85d010..54f5b153 100644 --- a/lib/keywords/maxProperties.js +++ b/lib/keywords/maxProperties.js @@ -1,11 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/maxProperties"; const compile = (schema) => Browser.value(schema); const interpret = (maxProperties, instance) => { - return instance.typeOf() !== "object" || [...instance.keys()].length <= maxProperties; + return Instance.typeOf(instance) !== "object" || [...Instance.keys(instance)].length <= maxProperties; }; export default { id, compile, interpret }; diff --git a/lib/keywords/maximum.js b/lib/keywords/maximum.js index cf21a99d..7eab9a2d 100644 --- a/lib/keywords/maximum.js +++ b/lib/keywords/maximum.js @@ -1,9 +1,10 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/maximum"; const compile = (schema) => Browser.value(schema); -const interpret = (maximum, instance) => instance.typeOf() !== "number" || instance.value() <= maximum; +const interpret = (maximum, instance) => Instance.typeOf(instance) !== "number" || Instance.value(instance) <= maximum; export default { id, compile, interpret }; diff --git a/lib/keywords/minItems.js b/lib/keywords/minItems.js index 18922a86..8cceb338 100644 --- a/lib/keywords/minItems.js +++ b/lib/keywords/minItems.js @@ -1,9 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/minItems"; const compile = (schema) => Browser.value(schema); -const interpret = (minItems, instance) => instance.typeOf() !== "array" || instance.length() >= minItems; +const interpret = (minItems, instance) => { + return Instance.typeOf(instance) !== "array" || Instance.length(instance) >= minItems; +}; export default { id, compile, interpret }; diff --git a/lib/keywords/minLength.js b/lib/keywords/minLength.js index c59fe68e..5e85a409 100644 --- a/lib/keywords/minLength.js +++ b/lib/keywords/minLength.js @@ -1,9 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/minLength"; const compile = (schema) => Browser.value(schema); -const interpret = (minLength, instance) => instance.typeOf() !== "string" || [...instance.value()].length >= minLength; +const interpret = (minLength, instance) => { + return Instance.typeOf(instance) !== "string" || [...Instance.value(instance)].length >= minLength; +}; export default { id, compile, interpret }; diff --git a/lib/keywords/minProperties.js b/lib/keywords/minProperties.js index e18c3a62..83abb137 100644 --- a/lib/keywords/minProperties.js +++ b/lib/keywords/minProperties.js @@ -1,11 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/minProperties"; const compile = (schema) => Browser.value(schema); const interpret = (minProperties, instance) => { - return instance.typeOf() !== "object" || [...instance.keys()].length >= minProperties; + return Instance.typeOf(instance) !== "object" || [...Instance.keys(instance)].length >= minProperties; }; export default { id, compile, interpret }; diff --git a/lib/keywords/minimum.js b/lib/keywords/minimum.js index caaf7187..c0c2a0a6 100644 --- a/lib/keywords/minimum.js +++ b/lib/keywords/minimum.js @@ -1,9 +1,10 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/minimum"; const compile = (schema) => Browser.value(schema); -const interpret = (minimum, instance) => instance.typeOf() !== "number" || instance.value() >= minimum; +const interpret = (minimum, instance) => Instance.typeOf(instance) !== "number" || Instance.value(instance) >= minimum; export default { id, compile, interpret }; diff --git a/lib/keywords/multipleOf.js b/lib/keywords/multipleOf.js index e555ae3b..dd80f381 100644 --- a/lib/keywords/multipleOf.js +++ b/lib/keywords/multipleOf.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/multipleOf"; @@ -6,11 +7,11 @@ const id = "https://json-schema.org/keyword/multipleOf"; const compile = (schema) => Browser.value(schema); const interpret = (multipleOf, instance) => { - if (instance.typeOf() !== "number") { + if (Instance.typeOf(instance) !== "number") { return true; } - const remainder = instance.value() % multipleOf; + const remainder = Instance.value(instance) % multipleOf; return numberEqual(0, remainder) || numberEqual(multipleOf, remainder); }; diff --git a/lib/keywords/pattern.js b/lib/keywords/pattern.js index 4e6af3ee..5bd5436e 100644 --- a/lib/keywords/pattern.js +++ b/lib/keywords/pattern.js @@ -1,9 +1,12 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/pattern"; const compile = (schema) => new RegExp(Browser.value(schema), "u"); -const interpret = (pattern, instance) => instance.typeOf() !== "string" || pattern.test(instance.value()); +const interpret = (pattern, instance) => { + return Instance.typeOf(instance) !== "string" || pattern.test(Instance.value(instance)); +}; export default { id, compile, interpret }; diff --git a/lib/keywords/patternProperties.js b/lib/keywords/patternProperties.js index ca822bfe..798229cb 100644 --- a/lib/keywords/patternProperties.js +++ b/lib/keywords/patternProperties.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectArray } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { Validation } from "../experimental.js"; @@ -15,14 +16,15 @@ const compile = (schema, ast) => pipe( ); const interpret = (patternProperties, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; for (const [pattern, schemaUri] of patternProperties) { - for (const [propertyName, propertyValue] of instance.entries()) { - if (pattern.test(propertyName.value()) && !Validation.interpret(schemaUri, propertyValue, ast, dynamicAnchors, quiet)) { + for (const [propertyNameNode, propertyValue] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (pattern.test(propertyName) && !Validation.interpret(schemaUri, propertyValue, ast, dynamicAnchors, quiet)) { isValid = false; } } @@ -32,19 +34,20 @@ const interpret = (patternProperties, instance, ast, dynamicAnchors, quiet) => { }; const collectEvaluatedProperties = (patternProperties, instance, ast, dynamicAnchors) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return false; } const evaluatedPropertyNames = new Set(); for (const [pattern, propertySchema] of patternProperties) { - for (const [propertyName, property] of instance.entries()) { - if (pattern.test(propertyName.value())) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (pattern.test(propertyName)) { if (!Validation.interpret(propertySchema, property, ast, dynamicAnchors, true)) { return false; } - evaluatedPropertyNames.add(propertyName.value()); + evaluatedPropertyNames.add(propertyName); } } } diff --git a/lib/keywords/prefixItems.js b/lib/keywords/prefixItems.js index 10dc51e3..4ceda4ce 100644 --- a/lib/keywords/prefixItems.js +++ b/lib/keywords/prefixItems.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectArray, zip } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { Validation } from "../experimental.js"; @@ -12,14 +13,14 @@ const compile = (schema, ast) => pipe( ); const interpret = (prefixItems, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "array") { + if (Instance.typeOf(instance) !== "array") { return true; } let isValid = true; let index = 0; - const instanceLength = instance.length(); - for (const [schemaUri, item] of zip(prefixItems, instance.iter())) { + const instanceLength = Instance.length(instance); + for (const [schemaUri, item] of zip(prefixItems, Instance.iter(instance))) { if (index >= instanceLength) { break; } diff --git a/lib/keywords/properties.js b/lib/keywords/properties.js index 8b42a3c7..5060e16e 100644 --- a/lib/keywords/properties.js +++ b/lib/keywords/properties.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectObject } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { Validation } from "../experimental.js"; @@ -12,13 +13,14 @@ const compile = (schema, ast) => pipe( ); const interpret = (properties, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; - for (const [propertyName, property] of instance.entries()) { - if (propertyName.value() in properties && !Validation.interpret(properties[propertyName.value()], property, ast, dynamicAnchors, quiet)) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (propertyName in properties && !Validation.interpret(properties[propertyName], property, ast, dynamicAnchors, quiet)) { isValid = false; } } @@ -27,18 +29,19 @@ const interpret = (properties, instance, ast, dynamicAnchors, quiet) => { }; const collectEvaluatedProperties = (properties, instance, ast, dynamicAnchors) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return false; } const evaluatedPropertyNames = new Set(); - for (const [propertyName, property] of instance.entries()) { - if (propertyName.value() in properties) { - if (!Validation.interpret(properties[propertyName.value()], property, ast, dynamicAnchors, true)) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (propertyName in properties) { + if (!Validation.interpret(properties[propertyName], property, ast, dynamicAnchors, true)) { return false; } - evaluatedPropertyNames.add(propertyName.value()); + evaluatedPropertyNames.add(propertyName); } } diff --git a/lib/keywords/propertyDependencies.js b/lib/keywords/propertyDependencies.js index 121b7f5b..3aebd5da 100644 --- a/lib/keywords/propertyDependencies.js +++ b/lib/keywords/propertyDependencies.js @@ -1,5 +1,6 @@ import { pipe, asyncMap, asyncCollectObject } from "@hyperjump/pact"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { Validation } from "../experimental.js"; @@ -20,16 +21,16 @@ const compile = (schema, ast) => { }; const interpret = (propertyDependencies, instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; - const instanceValue = instance.value(); + const instanceValue = Instance.value(instance); for (const [propertyName, valueMappings] of Object.entries(propertyDependencies)) { const propertyValue = instanceValue[propertyName]; if ( - instance.has(propertyName) + Instance.has(instance, propertyName) && propertyValue in valueMappings && !Validation.interpret(valueMappings[propertyValue], instance, ast, dynamicAnchors, quiet) ) { @@ -43,10 +44,10 @@ const interpret = (propertyDependencies, instance, ast, dynamicAnchors, quiet) = const collectEvaluatedProperties = (propertyDependencies, instance, ast, dynamicAnchors) => { const evaluatedPropertyNames = new Set(); for (const propertyName in propertyDependencies) { - const propertyValue = instance.value()[propertyName]; + const propertyValue = Instance.value(instance)[propertyName]; const valueMappings = propertyDependencies[propertyName]; - if (instance.has(propertyName) && propertyValue in valueMappings) { + if (Instance.has(instance, propertyName) && propertyValue in valueMappings) { const propertyNames = Validation.collectEvaluatedProperties(valueMappings[propertyValue], instance, ast, dynamicAnchors); if (!propertyNames) { return false; diff --git a/lib/keywords/propertyNames.js b/lib/keywords/propertyNames.js index 7e035c8b..24ff71b0 100644 --- a/lib/keywords/propertyNames.js +++ b/lib/keywords/propertyNames.js @@ -1,4 +1,5 @@ import { Validation } from "../experimental.js"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/propertyNames"; @@ -6,12 +7,12 @@ const id = "https://json-schema.org/keyword/propertyNames"; const compile = (schema, ast) => Validation.compile(schema, ast); const interpret = (propertyNames, instance, ast, dynamicAnchors) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } let isValid = true; - for (const key of instance.keys()) { + for (const key of Instance.keys(instance)) { if (!Validation.interpret(propertyNames, key, ast, dynamicAnchors, true)) { isValid = false; } diff --git a/lib/keywords/readOnly.js b/lib/keywords/readOnly.js index 55f9e834..9b3400de 100644 --- a/lib/keywords/readOnly.js +++ b/lib/keywords/readOnly.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/readOnly"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/readOnly"; const compile = (schema) => Browser.value(schema); const interpret = (readOnly, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, readOnly); + setAnnotation(instance, id, schemaLocation, readOnly); return true; }; diff --git a/lib/keywords/requireAllExcept.js b/lib/keywords/requireAllExcept.js index 5356c8e0..9f3135a5 100644 --- a/lib/keywords/requireAllExcept.js +++ b/lib/keywords/requireAllExcept.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; import { getKeywordName } from "../experimental.js"; @@ -16,7 +17,7 @@ const compile = async (schema, _ast, parentSchema) => { }; const interpret = (required, instance) => { - return instance.typeOf() !== "object" || required.every((propertyName) => Object.hasOwn(instance.value(), propertyName)); + return Instance.typeOf(instance) !== "object" || required.every((propertyName) => Object.hasOwn(Instance.value(instance), propertyName)); }; export default { id, compile, interpret }; diff --git a/lib/keywords/required.js b/lib/keywords/required.js index ec35faeb..fc49ab73 100644 --- a/lib/keywords/required.js +++ b/lib/keywords/required.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/required"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/required"; const compile = (schema) => Browser.value(schema); const interpret = (required, instance) => { - return instance.typeOf() !== "object" || required.every((propertyName) => Object.hasOwn(instance.value(), propertyName)); + return Instance.typeOf(instance) !== "object" || required.every((propertyName) => Object.hasOwn(Instance.value(instance), propertyName)); }; export default { id, compile, interpret }; diff --git a/lib/keywords/title.js b/lib/keywords/title.js index 45e78183..7e37c92a 100644 --- a/lib/keywords/title.js +++ b/lib/keywords/title.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/title"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/title"; const compile = (schema) => Browser.value(schema); const interpret = (title, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, title); + setAnnotation(instance, id, schemaLocation, title); return true; }; diff --git a/lib/keywords/type.js b/lib/keywords/type.js index 49befafb..44add038 100644 --- a/lib/keywords/type.js +++ b/lib/keywords/type.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/type"; @@ -9,7 +10,7 @@ const interpret = (type, instance) => typeof type === "string" : type.some(isTypeOf(instance)); const isTypeOf = (instance) => (type) => type === "integer" - ? instance.typeOf() === "number" && Number.isInteger(instance.value()) - : instance.typeOf() === type; + ? Instance.typeOf(instance) === "number" && Number.isInteger(Instance.value(instance)) + : Instance.typeOf(instance) === type; export default { id, compile, interpret }; diff --git a/lib/keywords/unevaluatedItems.js b/lib/keywords/unevaluatedItems.js index 6c45719e..8f9c28b1 100644 --- a/lib/keywords/unevaluatedItems.js +++ b/lib/keywords/unevaluatedItems.js @@ -1,4 +1,5 @@ import { zip, range } from "@hyperjump/pact"; +import * as Instance from "../instance.js"; import { canonicalUri } from "../schema.js"; import { Validation } from "../experimental.js"; @@ -10,7 +11,7 @@ const compile = async (schema, ast, parentSchema) => { }; const interpret = ([schemaUrl, unevaluatedItems], instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "array") { + if (Instance.typeOf(instance) !== "array") { return true; } @@ -20,7 +21,7 @@ const interpret = ([schemaUrl, unevaluatedItems], instance, ast, dynamicAnchors, } let isValid = true; - for (const [item, index] of zip(instance.iter(), range(0))) { + for (const [item, index] of zip(Instance.iter(instance), range(0))) { if (!itemIndexes.has(index) && !Validation.interpret(unevaluatedItems, item, ast, dynamicAnchors, quiet)) { isValid = false; } @@ -36,7 +37,7 @@ const collectEvaluatedItems = (keywordValue, instance, ast, dynamicAnchors) => { } const evaluatedIndexes = new Set(); - for (let ndx = 0; ndx < instance.length(); ndx++) { + for (let ndx = 0; ndx < Instance.length(instance); ndx++) { if (!itemIndexes.has(ndx)) { evaluatedIndexes.add(ndx); } diff --git a/lib/keywords/unevaluatedProperties.js b/lib/keywords/unevaluatedProperties.js index 462498b7..24749de5 100644 --- a/lib/keywords/unevaluatedProperties.js +++ b/lib/keywords/unevaluatedProperties.js @@ -1,4 +1,5 @@ import { Validation, canonicalUri } from "../experimental.js"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/unevaluatedProperties"; @@ -8,7 +9,7 @@ const compile = async (schema, ast, parentSchema) => { }; const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors, quiet) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } @@ -18,8 +19,9 @@ const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnc } let isValid = true; - for (const [propertyName, property] of instance.entries()) { - if (!evaluatedPropertyNames.has(propertyName.value()) && !Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, quiet)) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (!evaluatedPropertyNames.has(propertyName) && !Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, quiet)) { isValid = false; } } @@ -28,7 +30,7 @@ const interpret = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnc }; const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance, ast, dynamicAnchors) => { - if (instance.typeOf() !== "object") { + if (Instance.typeOf(instance) !== "object") { return true; } @@ -38,13 +40,14 @@ const collectEvaluatedProperties = ([schemaUrl, unevaluatedProperties], instance return false; } - for (const [propertyName, property] of instance.entries()) { - if (!evaluatedPropertyNames.has(propertyName.value())) { + for (const [propertyNameNode, property] of Instance.entries(instance)) { + const propertyName = Instance.value(propertyNameNode); + if (!evaluatedPropertyNames.has(propertyName)) { if (!Validation.interpret(unevaluatedProperties, property, ast, dynamicAnchors, true)) { return false; } - evaluatedPropertyNames.add(propertyName.value()); + evaluatedPropertyNames.add(propertyName); } } diff --git a/lib/keywords/uniqueItems.js b/lib/keywords/uniqueItems.js index 032480fa..6d28cf24 100644 --- a/lib/keywords/uniqueItems.js +++ b/lib/keywords/uniqueItems.js @@ -1,5 +1,6 @@ import jsonStringify from "json-stringify-deterministic"; import * as Browser from "@hyperjump/browser"; +import * as Instance from "../instance.js"; const id = "https://json-schema.org/keyword/uniqueItems"; @@ -7,11 +8,11 @@ const id = "https://json-schema.org/keyword/uniqueItems"; const compile = (schema) => Browser.value(schema); const interpret = (uniqueItems, instance) => { - if (instance.typeOf() !== "array" || uniqueItems === false) { + if (Instance.typeOf(instance) !== "array" || uniqueItems === false) { return true; } - const normalizedItems = instance.value().map(jsonStringify); + const normalizedItems = Instance.value(instance).map(jsonStringify); return new Set(normalizedItems).size === normalizedItems.length; }; diff --git a/lib/keywords/unknown.js b/lib/keywords/unknown.js index b409e1fa..d04065c3 100644 --- a/lib/keywords/unknown.js +++ b/lib/keywords/unknown.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/unknown"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/unknown"; const compile = (schema) => Browser.value(schema); const interpret = (value, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, value); + setAnnotation(instance, id, schemaLocation, value); return true; }; diff --git a/lib/keywords/validation.js b/lib/keywords/validation.js index fac02089..81f880da 100644 --- a/lib/keywords/validation.js +++ b/lib/keywords/validation.js @@ -53,14 +53,18 @@ const interpret = (url, instance, ast, dynamicAnchors, quiet = false) => { for (const [keywordId, schemaUrl, keywordValue] of ast[url]) { instance.valid = getKeyword(keywordId).interpret(keywordValue, instance, ast, dynamicAnchors, quiet, url); if (!instance.valid) { - !quiet && instance.error(schemaUrl, keywordId); + if (!quiet) { + instance.errors[schemaUrl] = keywordId; + } isSchemaValid = false; } } } if (!isSchemaValid) { - !quiet && instance.error(url, id); + if (!quiet) { + instance.errors[url] = id; + } } instance.valid = isSchemaValid; diff --git a/lib/keywords/writeOnly.js b/lib/keywords/writeOnly.js index c9d89d25..cc4ed5f9 100644 --- a/lib/keywords/writeOnly.js +++ b/lib/keywords/writeOnly.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../../annotations/index.js"; const id = "https://json-schema.org/keyword/writeOnly"; @@ -6,7 +7,7 @@ const id = "https://json-schema.org/keyword/writeOnly"; const compile = (schema) => Browser.value(schema); const interpret = (writeOnly, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, writeOnly); + setAnnotation(instance, id, schemaLocation, writeOnly); return true; }; diff --git a/lib/output.js b/lib/output.js index 3d94e322..88052d04 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1,4 +1,4 @@ -import { allNodes } from "./json-node.js"; +import { allNodes } from "./instance.js"; const outputFormats = {}; diff --git a/openapi-3-0/discriminator.js b/openapi-3-0/discriminator.js index abfefb4d..a0c731db 100644 --- a/openapi-3-0/discriminator.js +++ b/openapi-3-0/discriminator.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../annotations/index.js"; const id = "https://spec.openapis.org/oas/3.0/keyword/discriminator"; @@ -6,7 +7,7 @@ const id = "https://spec.openapis.org/oas/3.0/keyword/discriminator"; const compile = (schema) => Browser.value(schema); const interpret = (discriminator, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, discriminator); + setAnnotation(instance, id, schemaLocation, discriminator); return true; }; diff --git a/openapi-3-0/example.js b/openapi-3-0/example.js index e2d93a3c..f0cdc3f6 100644 --- a/openapi-3-0/example.js +++ b/openapi-3-0/example.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../annotations/index.js"; const id = "https://spec.openapis.org/oas/3.0/keyword/example"; @@ -6,7 +7,7 @@ const id = "https://spec.openapis.org/oas/3.0/keyword/example"; const compile = (schema) => Browser.value(schema); const interpret = (example, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, example); + setAnnotation(instance, id, schemaLocation, example); return true; }; diff --git a/openapi-3-0/externalDocs.js b/openapi-3-0/externalDocs.js index 6375056d..3fbcdadf 100644 --- a/openapi-3-0/externalDocs.js +++ b/openapi-3-0/externalDocs.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../annotations/index.js"; const id = "https://spec.openapis.org/oas/3.0/keyword/externalDocs"; @@ -6,7 +7,7 @@ const id = "https://spec.openapis.org/oas/3.0/keyword/externalDocs"; const compile = (schema) => Browser.value(schema); const interpret = (externalDocs, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, externalDocs); + setAnnotation(instance, id, schemaLocation, externalDocs); return true; }; diff --git a/openapi-3-0/json-schema-test-suite.spec.ts b/openapi-3-0/json-schema-test-suite.spec.ts index 19130de9..bccdefef 100644 --- a/openapi-3-0/json-schema-test-suite.spec.ts +++ b/openapi-3-0/json-schema-test-suite.spec.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import { describe, it, expect, beforeAll } from "vitest"; import { registerSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { OasSchema30, Validator } from "./index.js"; @@ -13,7 +14,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/openapi-3-0/type.js b/openapi-3-0/type.js index 5e94ec99..1cf47195 100644 --- a/openapi-3-0/type.js +++ b/openapi-3-0/type.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import * as Instance from "../lib/instance.js"; import { getKeywordName } from "../lib/experimental.js"; @@ -15,7 +16,7 @@ const interpret = (type, instance) => typeof type === "string" : type.some(isTypeOf(instance)); const isTypeOf = (instance) => (type) => type === "integer" - ? instance.typeOf() === "number" && Number.isInteger(instance.value()) - : instance.typeOf() === type; + ? Instance.typeOf(instance) === "number" && Number.isInteger(Instance.value(instance)) + : Instance.typeOf(instance) === type; export default { id, compile, interpret }; diff --git a/openapi-3-0/xml.js b/openapi-3-0/xml.js index c66c7539..ecc81c1a 100644 --- a/openapi-3-0/xml.js +++ b/openapi-3-0/xml.js @@ -1,4 +1,5 @@ import * as Browser from "@hyperjump/browser"; +import { setAnnotation } from "../annotations/index.js"; const id = "https://spec.openapis.org/oas/3.0/keyword/xml"; @@ -6,7 +7,7 @@ const id = "https://spec.openapis.org/oas/3.0/keyword/xml"; const compile = (schema) => Browser.value(schema); const interpret = (xml, instance, _ast, _dynamicAnchors, _quiet, schemaLocation) => { - instance.annotate(id, schemaLocation, xml); + setAnnotation(instance, id, schemaLocation, xml); return true; }; diff --git a/openapi-3-1/json-schema-test-suite.spec.ts b/openapi-3-1/json-schema-test-suite.spec.ts index 21daa175..ba524bec 100644 --- a/openapi-3-1/json-schema-test-suite.spec.ts +++ b/openapi-3-1/json-schema-test-suite.spec.ts @@ -3,6 +3,7 @@ import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { toAbsoluteIri } from "@hyperjump/uri"; import { registerSchema, unregisterSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { SchemaObject, OasSchema31, Validator } from "./index.js"; @@ -14,7 +15,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; }; diff --git a/stable/json-schema-test-suite.spec.ts b/stable/json-schema-test-suite.spec.ts index a8201436..cc738974 100644 --- a/stable/json-schema-test-suite.spec.ts +++ b/stable/json-schema-test-suite.spec.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { registerSchema, unregisterSchema, validate } from "./index.js"; +import type { Json } from "@hyperjump/json-pointer"; import type { JsonSchema, Validator } from "./index.js"; @@ -13,7 +14,7 @@ type Suite = { type Test = { description: string; - data: unknown; + data: Json; valid: boolean; };