Skip to content

Commit 5bbc78e

Browse files
committed
Convert unevaluated and dynamic into evaluation plugins
1 parent 64d8a2e commit 5bbc78e

37 files changed

+493
-563
lines changed

annotations/index.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
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+
dynamicPlugin,
9+
annotationsPlugin,
10+
basicOutputPlugin,
11+
detailedOutputPlugin
12+
} from "../lib/experimental.js";
413
import Validation from "../lib/keywords/validation.js";
514
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";
915

1016

1117
export const annotate = async (schemaUri, json = undefined, outputFormat = undefined) => {
@@ -17,7 +23,7 @@ export const annotate = async (schemaUri, json = undefined, outputFormat = undef
1723
};
1824

1925
export const interpret = ({ ast, schemaUri }, instance, outputFormat = BASIC) => {
20-
const context = { ast, plugins: [annotationsPlugin], dynamicAnchors: {} };
26+
const context = { ast, plugins: [annotationsPlugin, dynamicPlugin] };
2127

2228
switch (outputFormat) {
2329
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, dynamicPlugin, 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, dynamicPlugin] };
3832
const valid = Validation.interpret(schemaUri, instance, context);
3933

4034
const expectedOutput = {

bundle/test-suite.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
annotationsPlugin,
77
compile,
88
detailedOutputPlugin,
9+
dynamicPlugin,
910
getKeywordName,
1011
getSchema,
1112
Validation
@@ -68,10 +69,7 @@ const testRunner = (version: number, dialect: string) => {
6869
const instance = Instance.fromJs(test.instance);
6970
const context = {
7071
ast,
71-
plugins: [detailedOutputPlugin, annotationsPlugin],
72-
dynamicAnchors: {},
73-
errors: [],
74-
annotations: []
72+
plugins: [detailedOutputPlugin, annotationsPlugin, dynamicPlugin]
7573
} as ValidationContext & ErrorsContext & AnnotationsContext;
7674
const valid = Validation.interpret(schemaUri, instance, context);
7775

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: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,15 @@ 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);
29-
30-
export default { id, compile, interpret, simpleApplicator, collectEvaluatedProperties, collectEvaluatedItems };
26+
export default { id, compile, interpret, simpleApplicator };

lib/core.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getKeywordName } from "./keywords.js";
1313
import Validation from "./keywords/validation.js";
1414
import { basicOutputPlugin } from "./evaluation-plugins/basic-output.js";
1515
import { detailedOutputPlugin } from "./evaluation-plugins/detailed-output.js";
16+
import { dynamicPlugin } from "./evaluation-plugins/dynamic.js";
1617

1718

1819
export const FLAG = "FLAG", BASIC = "BASIC", DETAILED = "DETAILED";
@@ -33,7 +34,7 @@ export const compile = async (schema) => {
3334
};
3435

3536
export const interpret = curry(({ ast, schemaUri }, instance, outputFormat = FLAG) => {
36-
const context = { ast, plugins: [], dynamicAnchors: {} };
37+
const context = { ast, plugins: [dynamicPlugin] };
3738

3839
switch (outputFormat) {
3940
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/evaluation-plugins/dynamic.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { toAbsoluteUri } from "../common.js";
2+
3+
4+
export const dynamicPlugin = {
5+
beforeSchema(url, _instance, context) {
6+
context.dynamicAnchors = {
7+
...context.ast.metaData[toAbsoluteUri(url)].dynamicAnchors,
8+
...context.dynamicAnchors
9+
};
10+
},
11+
beforeKeyword(_url, _instance, context, schemaContext) {
12+
context.dynamicAnchors = schemaContext.dynamicAnchors;
13+
},
14+
afterKeyword() {
15+
},
16+
afterSchema() {
17+
}
18+
};

lib/experimental.d.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,20 @@ export type Keyword<A> = {
7070
compile: (schema: Browser<SchemaDocument>, ast: AST, parentSchema: Browser<SchemaDocument>) => Promise<A>;
7171
interpret: (compiledKeywordValue: A, instance: JsonNode, context: ValidationContext) => boolean;
7272
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;
7573
collectExternalIds?: (visited: Set<string>, parentSchema: Browser<SchemaDocument>, schema: Browser<SchemaDocument>) => Promise<Set<string>>;
7674
annotation?: <B>(compiledKeywordValue: A, instance: JsonNode) => B | undefined;
7775
};
7876

7977
export type ValidationContext = {
8078
ast: AST;
8179
plugins: EvaluationPlugin<unknow>[];
82-
dynamicAnchors: Anchors;
8380
};
8481

8582
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;
83+
beforeSchema(url: string, instance: JsonNode, context: Context): void;
84+
beforeKeyword(keywordNode: Node<unknown>, instance: JsonNode, context: Context, schemaContext: Context, keyword: Keyword): void;
85+
afterKeyword(keywordNode: Node<unknown>, instance: JsonNode, context: Context, valid: boolean, schemaContext: Context, keyword: Keyword): void;
86+
afterSchema(url: string, instance: JsonNode, context: Context, valid: boolean): void;
9087
};
9188

9289
export const basicOutputPlugin: EvaluationPlugin<ErrorsContext>;
@@ -100,4 +97,9 @@ export type AnnotationsContext = {
10097
annotations: OutputUnit[];
10198
};
10299

100+
export const dynamicPlugin: EvaluationPlugin<DynamicContext>;
101+
export type DynamicContext = {
102+
dynamicAnchors: Anchors;
103+
};
104+
103105
export const Validation: Keyword<string>;

lib/experimental.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { default as Validation } from "./keywords/validation.js";
99
export { basicOutputPlugin } from "./evaluation-plugins/basic-output.js";
1010
export { detailedOutputPlugin } from "./evaluation-plugins/detailed-output.js";
1111
export { annotationsPlugin } from "./evaluation-plugins/annotations.js";
12+
export { dynamicPlugin } from "./evaluation-plugins/dynamic.js";

0 commit comments

Comments
 (0)