Skip to content

Commit

Permalink
Refactor instances
Browse files Browse the repository at this point in the history
  • Loading branch information
jdesrosiers committed Apr 15, 2024
1 parent bdc59e2 commit a0e27ec
Show file tree
Hide file tree
Showing 446 changed files with 5,974 additions and 26,033 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -615,14 +615,13 @@ These are available from the `@hyperjump/json-schema/experimental` export.
A curried function for validating an instance against a compiled schema.
This can be useful for creating custom output formats.
* **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
* **OutputFormat**: **FLAG** | **BASIC**
In addition to the `FLAG` output format in the Stable API, the Experimental
API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
specified in the 2019-09 specification (with some minor customizations).
This implementation doesn't include annotations or human readable error
messages. The output can be processed to create human readable error
messages as needed.
API includes support for the `BASIC` format as specified in the 2019-09
specification (with some minor customizations). This implementation doesn't
include annotations or human readable error messages. The output can be
processed to create human readable error messages as needed.
## Instance API (experimental)
Expand Down
44 changes: 0 additions & 44 deletions annotations/annotated-instance.d.ts

This file was deleted.

47 changes: 0 additions & 47 deletions annotations/annotated-instance.js

This file was deleted.

6 changes: 3 additions & 3 deletions annotations/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { OutputFormat, OutputUnit } from "../lib/index.js";
import type { AnnotatedInstance } from "./annotated-instance.js";
import type { CompiledSchema } from "../lib/experimental.js";
import type { JsonNode } from "../lib/json-node.js";


export const annotate: (
Expand All @@ -9,9 +9,9 @@ export const annotate: (
(schemaUrl: string) => Promise<Annotator>
);

export const interpret: (compiledSchema: CompiledSchema, value: AnnotatedInstance, outputFormat?: OutputFormat) => AnnotatedInstance;
export const interpret: (compiledSchema: CompiledSchema, value: JsonNode, outputFormat?: OutputFormat) => JsonNode;

export type Annotator = (value: unknown, outputFormat?: OutputFormat) => AnnotatedInstance;
export type Annotator = (value: unknown, outputFormat?: OutputFormat) => JsonNode;

export class ValidationError extends Error {
public output: OutputUnit;
Expand Down
115 changes: 7 additions & 108 deletions annotations/index.js
Original file line number Diff line number Diff line change
@@ -1,124 +1,23 @@
import { subscribe, unsubscribe } from "../lib/pubsub.js";
import { AnnotatedJsInstance } from "./annotated-instance.js";
import { ValidationError } from "./validation-error.js";
import { getSchema, getKeyword, compile, interpret as validate, BASIC } from "../lib/experimental.js";
import { getSchema, compile, interpret as validate, BASIC } from "../lib/experimental.js";
import { jsonNodeFromJs } from "../lib/json-node.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, new AnnotatedJsInstance(json), outputFormat);
const interpretAst = (json, outputFormat) => interpret(compiled, jsonNodeFromJs(json), outputFormat);

return json === undefined ? interpretAst : interpretAst(json, outputFormat);
};

export const interpret = ({ ast, schemaUri }, instance, outputFormat = BASIC) => {
loadKeywordSupport();

const output = [instance];
const subscriptionToken = subscribe("result", outputHandler(output));

try {
const result = validate({ ast, schemaUri }, instance, outputFormat);
if (!result.valid) {
throw new ValidationError(result);
}
} finally {
unsubscribe("result", subscriptionToken);
}

return output[0];
};

const outputHandler = (output) => {
let isPassing = true;
const instanceStack = [];

return (message, resultNode) => {
if (message === "result.start") {
instanceStack.push(output[0]);
isPassing = true;
} else if (message === "result" && isPassing) {
output[0] = output[0].get(resultNode.instanceLocation);

if (resultNode.valid) {
const keywordHandler = getKeyword(resultNode.keyword);
if (keywordHandler?.annotation) {
const annotation = keywordHandler.annotation(resultNode.ast);
output[0] = output[0].annotate(resultNode.keyword, annotation);
}
} else {
output[0] = instanceStack[instanceStack.length - 1];
isPassing = false;
}
} else if (message === "result.end") {
instanceStack.pop();
}
};
};

const identity = (a) => a;

const loadKeywordSupport = () => {
const title = getKeyword("https://json-schema.org/keyword/title");
if (title) {
title.annotation = title.annotation ?? identity;
}

const description = getKeyword("https://json-schema.org/keyword/description");
if (description) {
description.annotation = description.annotation ?? identity;
}

const _default = getKeyword("https://json-schema.org/keyword/default");
if (_default) {
_default.annotation = _default.annotation ?? identity;
}

const deprecated = getKeyword("https://json-schema.org/keyword/deprecated");
if (deprecated) {
deprecated.annotation = deprecated.annotation ?? identity;
}

const readOnly = getKeyword("https://json-schema.org/keyword/readOnly");
if (readOnly) {
readOnly.annotation = readOnly.annotation ?? identity;
}

const writeOnly = getKeyword("https://json-schema.org/keyword/writeOnly");
if (writeOnly) {
writeOnly.annotation = writeOnly.annotation ?? identity;
}

const examples = getKeyword("https://json-schema.org/keyword/examples");
if (examples) {
examples.annotation = examples.annotation ?? identity;
}

const format = getKeyword("https://json-schema.org/keyword/format");
if (format) {
format.annotation = format.annotation ?? identity;
}

const contentMediaType = getKeyword("https://json-schema.org/keyword/contentMediaType");
if (contentMediaType) {
contentMediaType.annotation = contentMediaType.annotation ?? identity;
}

const contentEncoding = getKeyword("https://json-schema.org/keyword/contentEncoding");
if (contentEncoding) {
contentEncoding.annotation = contentEncoding.annotation ?? identity;
}

const contentSchema = getKeyword("https://json-schema.org/keyword/contentSchema");
if (contentSchema) {
contentSchema.annotation = contentSchema.annotation ?? identity;
const result = validate({ ast, schemaUri }, instance, outputFormat);
if (!result.valid) {
throw new ValidationError(result);
}

const unknown = getKeyword("https://json-schema.org/keyword/unknown");
if (unknown) {
unknown.annotation = unknown.annotation ?? identity;
}
return instance;
};

export { ValidationError } from "./validation-error.js";
6 changes: 3 additions & 3 deletions annotations/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import "../draft-04/index.js";

import type { SchemaObject } from "../lib/index.js";
import type { Annotator } from "./index.js";
import type { AnnotatedInstance } from "./annotated-instance.js";
import type { JsonNode } from "../lib/json-node.js";


type Suite = {
Expand Down Expand Up @@ -66,7 +66,7 @@ describe("Annotations", () => {

suite.subjects.forEach((subject) => {
describe("Instance: " + JSON.stringify(subject.instance), () => {
let instance: AnnotatedInstance;
let instance: JsonNode;

beforeEach(() => {
instance = annotator(subject.instance);
Expand All @@ -76,7 +76,7 @@ describe("Annotations", () => {
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);
?.annotation(assertion.keyword, dialect) ?? [];
expect(annotations).to.eql(assertion.expected);
});
});
Expand Down
12 changes: 8 additions & 4 deletions bundle/generate-snapshots.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { writeFile, mkdir, rm } from "node:fs/promises";
import { isCompatible, md5, loadSchemas, testSuite, unloadSchemas } from "./test-utils.js";
import { validate } from "../lib/index.js";
import { VERBOSE } from "../lib/experimental.js";
import { isCompatible, md5, loadSchemas, testSuite, toOutput, unloadSchemas } from "./test-utils.js";
import { compile, getSchema, interpret } from "../lib/experimental.js";
import "../stable/index.js";
import "../draft-2020-12/index.js";
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";


const suite = testSuite("./bundle/tests");
Expand All @@ -23,7 +23,11 @@ const snapshotGenerator = async (version, dialect) => {
let testIndex = 0;
for (const test of testCase.tests) {
loadSchemas(testCase, mainSchemaUri, dialect);
const expectedOutput = await validate(mainSchemaUri, test.instance, VERBOSE);
const schema = await getSchema(mainSchemaUri);
const compiledSchema = await compile(schema);
const instance = jsonNodeFromJs(test.instance);
interpret(compiledSchema, instance);
const expectedOutput = toOutput(instance);
unloadSchemas(testCase, mainSchemaUri);

const testId = md5(`${version}|${dialect}|${testCase.description}|${testIndex}`);
Expand Down
25 changes: 6 additions & 19 deletions bundle/snapshots/0176f79e8816433f3b23683ba5f38ace
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
{
"keyword": "https://json-schema.org/evaluation/validate",
"absoluteKeywordLocation": "https://bundler.hyperjump.io/main#",
"instanceLocation": "#",
"valid": false,
"errors": [
{
"keyword": "https://json-schema.org/keyword/type",
"absoluteKeywordLocation": "https://bundler.hyperjump.io/main#/type",
"instanceLocation": "#",
"valid": false,
"errors": []
"#": {
"errors": {
"https://bundler.hyperjump.io/main#/type": "https://json-schema.org/keyword/type",
"https://bundler.hyperjump.io/main#": "https://json-schema.org/evaluation/validate"
},
{
"keyword": "https://json-schema.org/keyword/definitions",
"absoluteKeywordLocation": "https://bundler.hyperjump.io/main#/$defs",
"instanceLocation": "#",
"valid": true,
"errors": []
}
]
"annotations": {}
}
}
Loading

0 comments on commit a0e27ec

Please sign in to comment.