Skip to content

Commit 61cc18d

Browse files
committed
Convert unevaluated and dynamic into evaluation plugins
1 parent 64d8a2e commit 61cc18d

35 files changed

+504
-564
lines changed

annotations/index.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { FLAG } from "../lib/index.js";
22
import { ValidationError } from "./validation-error.js";
3-
import { getSchema, compile, BASIC, DETAILED } from "../lib/experimental.js";
3+
import {
4+
getSchema,
5+
compile,
6+
BASIC,
7+
DETAILED,
8+
annotationsPlugin,
9+
basicOutputPlugin,
10+
detailedOutputPlugin
11+
} from "../lib/experimental.js";
412
import Validation from "../lib/keywords/validation.js";
513
import * as Instance from "../lib/instance.js";
6-
import { annotationsPlugin } from "../lib/evaluation-plugins/annotations.js";
7-
import { basicOutputPlugin } from "../lib/evaluation-plugins/basic-output.js";
8-
import { detailedOutputPlugin } from "../lib/evaluation-plugins/detailed-output.js";
914

1015

1116
export const annotate = async (schemaUri, json = undefined, outputFormat = undefined) => {
@@ -17,7 +22,7 @@ export const annotate = async (schemaUri, json = undefined, outputFormat = undef
1722
};
1823

1924
export const interpret = ({ ast, schemaUri }, instance, outputFormat = BASIC) => {
20-
const context = { ast, plugins: [annotationsPlugin], dynamicAnchors: {} };
25+
const context = { ast, plugins: [annotationsPlugin, ...ast.plugins] };
2126

2227
switch (outputFormat) {
2328
case FLAG:

bundle/generate-snapshots.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { writeFile, mkdir, rm } from "node:fs/promises";
22
import { isCompatible, md5, loadSchemas, testSuite, unloadSchemas } from "./test-utils.js";
3-
import { compile, getSchema, Validation } from "../lib/experimental.js";
3+
import { annotationsPlugin, compile, detailedOutputPlugin, getSchema, Validation } from "../lib/experimental.js";
44
import "../stable/index.js";
55
import "../draft-2020-12/index.js";
66
import "../draft-2019-09/index.js";
77
import "../draft-07/index.js";
88
import "../draft-06/index.js";
99
import "../draft-04/index.js";
1010
import * as Instance from "../lib/instance.js";
11-
import { detailedOutputPlugin } from "../lib/evaluation-plugins/detailed-output.js";
12-
import { annotationsPlugin } from "../lib/evaluation-plugins/annotations.js";
1311

1412

1513
const suite = testSuite("./bundle/tests");
@@ -30,11 +28,7 @@ const snapshotGenerator = async (version, dialect) => {
3028
const { ast, schemaUri } = await compile(schema);
3129

3230
const instance = Instance.fromJs(test.instance);
33-
const context = {
34-
ast,
35-
plugins: [detailedOutputPlugin, annotationsPlugin],
36-
dynamicAnchors: {}
37-
};
31+
const context = { ast, plugins: [detailedOutputPlugin, annotationsPlugin, ...ast.plugins] };
3832
const valid = Validation.interpret(schemaUri, instance, context);
3933

4034
const expectedOutput = {

bundle/test-suite.spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,7 @@ const testRunner = (version: number, dialect: string) => {
6868
const instance = Instance.fromJs(test.instance);
6969
const context = {
7070
ast,
71-
plugins: [detailedOutputPlugin, annotationsPlugin],
72-
dynamicAnchors: {},
73-
errors: [],
74-
annotations: []
71+
plugins: [detailedOutputPlugin, annotationsPlugin, ...ast.plugins]
7572
} as ValidationContext & ErrorsContext & AnnotationsContext;
7673
const valid = Validation.interpret(schemaUri, instance, context);
7774

draft-04/additionalItems.js

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { pipe, drop, every } from "@hyperjump/pact";
1+
import { drop } from "@hyperjump/pact";
22
import * as Browser from "@hyperjump/browser";
33
import * as Instance from "../lib/instance.js";
44
import { getKeywordName, Validation } from "../lib/experimental.js";
@@ -19,26 +19,20 @@ const interpret = ([numberOfItems, additionalItems], instance, context) => {
1919
return true;
2020
}
2121

22-
return pipe(
23-
Instance.iter(instance),
24-
drop(numberOfItems),
25-
every((item) => Validation.interpret(additionalItems, item, context))
26-
);
27-
};
28-
29-
const simpleApplicator = true;
22+
let isValid = true;
23+
let index = numberOfItems;
24+
for (const item of drop(numberOfItems, Instance.iter(instance))) {
25+
if (!Validation.interpret(additionalItems, item, context)) {
26+
isValid = false;
27+
}
3028

31-
const collectEvaluatedItems = (keywordValue, instance, context) => {
32-
if (!interpret(keywordValue, instance, context)) {
33-
return false;
29+
context.evaluatedItems?.add(index);
30+
index++;
3431
}
3532

36-
const evaluatedIndexes = new Set();
37-
for (let ndx = keywordValue[0]; ndx < Instance.length(instance); ndx++) {
38-
evaluatedIndexes.add(ndx);
39-
}
40-
41-
return evaluatedIndexes;
33+
return isValid;
4234
};
4335

44-
export default { id, compile, interpret, simpleApplicator, collectEvaluatedItems };
36+
const simpleApplicator = true;
37+
38+
export default { id, compile, interpret, simpleApplicator };

draft-04/items.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { pipe, asyncMap, asyncCollectArray, every, zip, take, range, collectSet } from "@hyperjump/pact";
1+
import { pipe, asyncMap, asyncCollectArray, zip } from "@hyperjump/pact";
22
import * as Browser from "@hyperjump/browser";
33
import * as Instance from "../lib/instance.js";
44
import { Validation } from "../lib/experimental.js";
@@ -23,23 +23,35 @@ const interpret = (items, instance, context) => {
2323
return true;
2424
}
2525

26+
let isValid = true;
27+
let index = 0;
28+
2629
if (typeof items === "string") {
27-
return every((itemValue) => Validation.interpret(items, itemValue, context), Instance.iter(instance));
30+
for (const item of Instance.iter(instance)) {
31+
if (!Validation.interpret(items, item, context)) {
32+
isValid = false;
33+
}
34+
35+
context.evaluatedItems?.add(index++);
36+
}
2837
} else {
29-
return pipe(
30-
zip(items, Instance.iter(instance)),
31-
take(Instance.length(instance)),
32-
every(([prefixItem, item]) => Validation.interpret(prefixItem, item, context))
33-
);
38+
for (const [tupleItem, tupleInstance] of zip(items, Instance.iter(instance))) {
39+
if (!tupleInstance) {
40+
break;
41+
}
42+
43+
if (!Validation.interpret(tupleItem, tupleInstance, context)) {
44+
isValid = false;
45+
}
46+
47+
context.evaluatedItems?.add(index);
48+
index++;
49+
}
3450
}
51+
52+
return isValid;
3553
};
3654

3755
const simpleApplicator = true;
3856

39-
const collectEvaluatedItems = (items, instance, context) => {
40-
return interpret(items, instance, context) && (typeof items === "string"
41-
? collectSet(range(0, Instance.length(instance)))
42-
: collectSet(range(0, items.length)));
43-
};
44-
45-
export default { id, compile, interpret, simpleApplicator, collectEvaluatedItems };
57+
export default { id, compile, interpret, simpleApplicator };

draft-2020-12/dynamicRef.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as Browser from "@hyperjump/browser";
22
import { Validation, canonicalUri } from "../lib/experimental.js";
3-
import { uriFragment } from "../lib/common.js";
3+
import { toAbsoluteUri, uriFragment } from "../lib/common.js";
44

55

66
const id = "https://json-schema.org/keyword/draft-2020-12/dynamicRef";
@@ -12,19 +12,31 @@ const compile = async (dynamicRef, ast) => {
1212
return [referencedSchema.document.baseUri, fragment, canonicalUri(referencedSchema)];
1313
};
1414

15-
const evaluate = (strategy, [id, fragment, ref], instance, context) => {
15+
const interpret = ([id, fragment, ref], instance, context) => {
1616
if (fragment in context.ast.metaData[id].dynamicAnchors) {
1717
context.dynamicAnchors = { ...context.ast.metaData[id].dynamicAnchors, ...context.dynamicAnchors };
18-
return strategy(context.dynamicAnchors[fragment], instance, context);
18+
return Validation.interpret(context.dynamicAnchors[fragment], instance, context);
1919
} else {
20-
return strategy(ref, instance, context);
20+
return Validation.interpret(ref, instance, context);
2121
}
2222
};
2323

2424
const simpleApplicator = true;
2525

26-
const interpret = (...args) => evaluate(Validation.interpret, ...args);
27-
const collectEvaluatedProperties = (...args) => evaluate(Validation.collectEvaluatedProperties, ...args);
28-
const collectEvaluatedItems = (...args) => evaluate(Validation.collectEvaluatedItems, ...args);
26+
const plugin = {
27+
beforeSchema(url, _instance, context) {
28+
context.dynamicAnchors = {
29+
...context.ast.metaData[toAbsoluteUri(url)].dynamicAnchors,
30+
...context.dynamicAnchors
31+
};
32+
},
33+
beforeKeyword(_url, _instance, context, schemaContext) {
34+
context.dynamicAnchors = schemaContext.dynamicAnchors;
35+
},
36+
afterKeyword() {
37+
},
38+
afterSchema() {
39+
}
40+
};
2941

30-
export default { id, compile, interpret, simpleApplicator, collectEvaluatedProperties, collectEvaluatedItems };
42+
export default { id, compile, interpret, simpleApplicator, plugin };

lib/core.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ export const validate = async (url, value = undefined, outputFormat = undefined)
2727
};
2828

2929
export const compile = async (schema) => {
30-
const ast = { metaData: {} };
30+
const ast = { metaData: {}, plugins: [] };
3131
const schemaUri = await Validation.compile(schema, ast);
3232
return { ast, schemaUri };
3333
};
3434

3535
export const interpret = curry(({ ast, schemaUri }, instance, outputFormat = FLAG) => {
36-
const context = { ast, plugins: [], dynamicAnchors: {} };
36+
const context = { ast, plugins: [...ast.plugins] };
3737

3838
switch (outputFormat) {
3939
case FLAG:

lib/evaluation-plugins/annotations.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ import * as Instance from "../instance.js";
22

33

44
export const annotationsPlugin = {
5-
beforeSchema(context) {
5+
beforeSchema(_url, _instance, context) {
66
context.annotations ??= [];
77
context.schemaAnnotations = [];
88
},
9-
beforeKeyword(context) {
9+
beforeKeyword(_node, _instance, context) {
1010
context.annotations = [];
1111
},
12-
afterKeyword(node, instance, valid, keywordContext, schemaContext, keyword) {
12+
afterKeyword(node, instance, context, valid, schemaContext, keyword) {
1313
if (valid) {
1414
const [keywordId, schemaUri, keywordValue] = node;
15-
const annotation = keyword.annotation?.(keywordValue, instance, keywordContext);
15+
const annotation = keyword.annotation?.(keywordValue, instance, context);
1616
if (annotation !== undefined) {
1717
schemaContext.schemaAnnotations.push({
1818
keyword: keywordId,
@@ -21,10 +21,10 @@ export const annotationsPlugin = {
2121
annotation: annotation
2222
});
2323
}
24-
schemaContext.schemaAnnotations.push(...keywordContext.annotations);
24+
schemaContext.schemaAnnotations.push(...context.annotations);
2525
}
2626
},
27-
afterSchema(_schemaNode, _instanceNode, valid, context) {
27+
afterSchema(_schemaNode, _instanceNode, context, valid) {
2828
if (valid) {
2929
context.annotations.push(...context.schemaAnnotations);
3030
}

lib/evaluation-plugins/basic-output.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import * as Instance from "../instance.js";
33

44

55
export const basicOutputPlugin = {
6-
beforeSchema(schemaContext) {
7-
schemaContext.errors ??= [];
6+
beforeSchema(_url, _intance, context) {
7+
context.errors ??= [];
88
},
9-
beforeKeyword(keywordContext) {
10-
keywordContext.errors = [];
9+
beforeKeyword(_node, _instance, context) {
10+
context.errors = [];
1111
},
12-
afterKeyword(node, instance, valid, keywordContext, schemaContext, keyword) {
12+
afterKeyword(node, instance, context, valid, schemaContext, keyword) {
1313
if (!valid) {
1414
if (!keyword.simpleApplicator) {
1515
const [keywordId, schemaUri] = node;
@@ -19,12 +19,12 @@ export const basicOutputPlugin = {
1919
instanceLocation: Instance.uri(instance)
2020
});
2121
}
22-
schemaContext.errors.push(...keywordContext.errors);
22+
schemaContext.errors.push(...context.errors);
2323
}
2424
},
25-
afterSchema(url, instance, valid, schemaContext) {
26-
if (typeof schemaContext.ast[url] === "boolean" && !valid) {
27-
schemaContext.errors.push({
25+
afterSchema(url, instance, context, valid) {
26+
if (typeof context.ast[url] === "boolean" && !valid) {
27+
context.errors.push({
2828
keyword: Validation.id,
2929
absoluteKeywordLocation: url,
3030
instanceLocation: Instance.uri(instance)

lib/evaluation-plugins/detailed-output.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import * as Instance from "../instance.js";
33

44

55
export const detailedOutputPlugin = {
6-
beforeSchema(schemaContext) {
7-
schemaContext.errors ??= [];
6+
beforeSchema(_url, _instance, context) {
7+
context.errors ??= [];
88
},
9-
beforeKeyword(keywordContext) {
10-
keywordContext.errors = [];
9+
beforeKeyword(_node, _instance, context) {
10+
context.errors = [];
1111
},
12-
afterKeyword(node, instance, valid, keywordContext, schemaContext) {
12+
afterKeyword(node, instance, context, valid, schemaContext) {
1313
if (!valid) {
1414
const [keywordId, schemaUri] = node;
1515
const outputUnit = {
@@ -19,14 +19,14 @@ export const detailedOutputPlugin = {
1919
};
2020

2121
schemaContext.errors.push(outputUnit);
22-
if (keywordContext.errors.length > 0) {
23-
outputUnit.errors = keywordContext.errors;
22+
if (context.errors.length > 0) {
23+
outputUnit.errors = context.errors;
2424
}
2525
}
2626
},
27-
afterSchema(url, instance, valid, schemaContext) {
28-
if (typeof schemaContext.ast[url] === "boolean" && !valid) {
29-
schemaContext.errors.push({
27+
afterSchema(url, instance, context, valid) {
28+
if (typeof context.ast[url] === "boolean" && !valid) {
29+
context.errors.push({
3030
keyword: Validation.id,
3131
absoluteKeywordLocation: url,
3232
instanceLocation: Instance.uri(instance)

lib/experimental.d.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type CompiledSchema = {
1818

1919
type AST = {
2020
metaData: Record<string, MetaData>;
21+
plugins: EvaluationPlugin<unknown>[];
2122
} & Record<string, Node<unknown>[] | boolean>;
2223

2324
type Node<A> = [keywordId: string, schemaUri: string, keywordValue: A];
@@ -70,23 +71,20 @@ export type Keyword<A> = {
7071
compile: (schema: Browser<SchemaDocument>, ast: AST, parentSchema: Browser<SchemaDocument>) => Promise<A>;
7172
interpret: (compiledKeywordValue: A, instance: JsonNode, context: ValidationContext) => boolean;
7273
simpleApplicator: boolean;
73-
collectEvaluatedProperties?: (compiledKeywordValue: A, instance: JsonNode, context: ValidationContext, isTop?: boolean) => Set<string> | false;
74-
collectEvaluatedItems?: (compiledKeywordValue: A, instance: JsonNode, context: ValidationContext, isTop?: boolean) => Set<number> | false;
7574
collectExternalIds?: (visited: Set<string>, parentSchema: Browser<SchemaDocument>, schema: Browser<SchemaDocument>) => Promise<Set<string>>;
7675
annotation?: <B>(compiledKeywordValue: A, instance: JsonNode) => B | undefined;
7776
};
7877

7978
export type ValidationContext = {
8079
ast: AST;
81-
plugins: EvaluationPlugin<unknow>[];
82-
dynamicAnchors: Anchors;
80+
plugins: EvaluationPlugin<unknown>[];
8381
};
8482

8583
export type EvaluationPlugin<Context> = {
86-
beforeSchema(schemaContext: Context): void;
87-
beforeKeyword(keywordContext: Context, schemaContext: Context): void;
88-
afterKeyword(keywordNode: JsonNode, instanceNode: JsonNode, valid: boolean, keywordContext: Context, schemaContext: Context, keyword: Keyword): void;
89-
afterSchema(schemaNode: JsonNode, instanceNode: JsonNode, valid: boolean, schemaContext: Context): void;
84+
beforeSchema(url: string, instance: JsonNode, context: Context): void;
85+
beforeKeyword(keywordNode: Node<unknown>, instance: JsonNode, context: Context, schemaContext: Context, keyword: Keyword): void;
86+
afterKeyword(keywordNode: Node<unknown>, instance: JsonNode, context: Context, valid: boolean, schemaContext: Context, keyword: Keyword): void;
87+
afterSchema(url: string, instance: JsonNode, context: Context, valid: boolean): void;
9088
};
9189

9290
export const basicOutputPlugin: EvaluationPlugin<ErrorsContext>;

0 commit comments

Comments
 (0)