@hyperjump/json-schema 1.16.2 → 1.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import type { OutputFormat, OutputUnit, ValidationOptions } from "../lib/index.js";
1
+ import type { OutputFormat, Output, ValidationOptions } from "../lib/index.js";
2
2
  import type { CompiledSchema } from "../lib/experimental.js";
3
- import type { JsonNode } from "../lib/json-node.js";
3
+ import type { JsonNode } from "../lib/instance.js";
4
4
  import type { Json } from "@hyperjump/json-pointer";
5
5
 
6
6
 
@@ -15,7 +15,7 @@ export type Annotator = (value: Json, options?: OutputFormat | ValidationOptions
15
15
  export const interpret: (compiledSchema: CompiledSchema, value: JsonNode, options?: OutputFormat | ValidationOptions) => JsonNode;
16
16
 
17
17
  export class ValidationError extends Error {
18
- public output: OutputUnit;
18
+ public output: Output & { valid: false };
19
19
 
20
- public constructor(output: OutputUnit);
20
+ public constructor(output: Output);
21
21
  }
@@ -1,12 +1,12 @@
1
1
  import type { Browser, Document } from "@hyperjump/browser";
2
- import type { Validator, OutputUnit, OutputFormat, SchemaObject } from "./index.js";
2
+ import type { Validator, Output, OutputUnit, OutputFormat, SchemaObject } from "./index.js";
3
3
  import type { JsonNode } from "./instance.js";
4
4
 
5
5
 
6
6
  // Compile/interpret
7
7
  export const compile: (schema: Browser<SchemaDocument>) => Promise<CompiledSchema>;
8
8
  export const interpret: (
9
- (compiledSchema: CompiledSchema, value: JsonNode, outputFormat?: OutputFormat) => OutputUnit
9
+ (compiledSchema: CompiledSchema, value: JsonNode, outputFormat?: OutputFormat) => Output
10
10
  ) & (
11
11
  (compiledSchema: CompiledSchema) => Validator
12
12
  );
package/lib/index.d.ts CHANGED
@@ -23,12 +23,19 @@ export type ValidationOptions = {
23
23
  };
24
24
 
25
25
  export const validate: (
26
- (url: string, value: Json, options?: OutputFormat | ValidationOptions) => Promise<OutputUnit>
26
+ (url: string, value: Json, options?: OutputFormat | ValidationOptions) => Promise<Output>
27
27
  ) & (
28
28
  (url: string) => Promise<Validator>
29
29
  );
30
30
 
31
- export type Validator = (value: Json, options?: OutputFormat | ValidationOptions) => OutputUnit;
31
+ export type Validator = (value: Json, options?: OutputFormat | ValidationOptions) => Output;
32
+
33
+ export type Output = {
34
+ valid: true;
35
+ } | {
36
+ valid: false;
37
+ errors?: OutputUnit[];
38
+ };
32
39
 
33
40
  export type OutputUnit = {
34
41
  keyword: string;
@@ -49,7 +56,7 @@ export const setShouldValidateSchema: (isEnabled: boolean) => void;
49
56
  export const getShouldValidateSchema: () => boolean;
50
57
 
51
58
  export class InvalidSchemaError extends Error {
52
- public output: OutputUnit;
59
+ public output: Output & { valid: false };
53
60
 
54
- public constructor(output: OutputUnit);
61
+ public constructor(output: Output);
55
62
  }
@@ -1,31 +1,17 @@
1
- import { canonicalUri, Validation } from "../experimental.js";
1
+ import { Validation } from "../experimental.js";
2
2
  import * as Instance from "../instance.js";
3
3
 
4
4
 
5
5
  const id = "https://json-schema.org/keyword/unevaluatedItems";
6
6
 
7
- const compile = async (schema, ast, parentSchema) => {
8
- return [canonicalUri(parentSchema), await Validation.compile(schema, ast)];
9
- };
7
+ const compile = (schema, ast) => Validation.compile(schema, ast);
10
8
 
11
- const interpret = ([schemaUrl, unevaluatedItems], instance, context) => {
9
+ const interpret = (unevaluatedItems, instance, context) => {
12
10
  if (Instance.typeOf(instance) !== "array") {
13
11
  return true;
14
12
  }
15
13
 
16
- // Because order matters, we re-evaluate this schema skipping this keyword
17
- // just to collect all the evalauted items.
18
- if (context.rootSchema === schemaUrl) {
19
- return true;
20
- }
21
- const evaluatedItemsPlugin = new EvaluatedItemsPlugin(schemaUrl);
22
- if (!Validation.interpret(schemaUrl, instance, {
23
- ...context,
24
- plugins: [...context.ast.plugins, evaluatedItemsPlugin]
25
- })) {
26
- return true;
27
- }
28
- const evaluatedItems = evaluatedItemsPlugin.evaluatedItems;
14
+ const evaluatedItems = context.schemaEvaluatedItems;
29
15
 
30
16
  let isValid = true;
31
17
  let index = 0;
@@ -46,38 +32,32 @@ const interpret = ([schemaUrl, unevaluatedItems], instance, context) => {
46
32
 
47
33
  const simpleApplicator = true;
48
34
 
49
- class EvaluatedItemsPlugin {
50
- constructor(rootSchema) {
51
- this.rootSchema = rootSchema;
52
- }
53
-
54
- beforeSchema(_url, _instance, context) {
35
+ const plugin = {
36
+ beforeSchema(_url, instance, context) {
55
37
  context.evaluatedItems ??= new Set();
56
- context.schemaEvaluatedItems ??= new Set();
57
- }
38
+ context.schemaEvaluatedItems = new Set();
39
+ context.instanceLocation ??= Instance.uri(instance);
40
+ },
58
41
 
59
- beforeKeyword(_node, _instance, context) {
60
- context.rootSchema = this.rootSchema;
42
+ beforeKeyword(_node, instance, context, schemaContext) {
61
43
  context.evaluatedItems = new Set();
62
- }
44
+ context.schemaEvaluatedItems = schemaContext.schemaEvaluatedItems;
45
+ context.instanceLocation = Instance.uri(instance);
46
+ },
63
47
 
64
- afterKeyword(_node, _instance, context, valid, schemaContext) {
65
- if (valid) {
66
- for (const index of context.evaluatedItems) {
67
- schemaContext.schemaEvaluatedItems.add(index);
68
- }
48
+ afterKeyword(_node, _instance, context, _valid, schemaContext) {
49
+ for (const property of context.evaluatedItems) {
50
+ schemaContext.schemaEvaluatedItems.add(property);
69
51
  }
70
- }
52
+ },
71
53
 
72
- afterSchema(_url, _instance, context, valid) {
73
- if (valid) {
74
- for (const index of context.schemaEvaluatedItems) {
75
- context.evaluatedItems.add(index);
54
+ afterSchema(_node, instance, context, valid) {
55
+ if (valid && Instance.uri(instance) === context.instanceLocation) {
56
+ for (const property of context.schemaEvaluatedItems) {
57
+ context.evaluatedItems.add(property);
76
58
  }
77
59
  }
78
-
79
- this.evaluatedItems = context.evaluatedItems;
80
60
  }
81
- }
61
+ };
82
62
 
83
- export default { id, compile, interpret, simpleApplicator };
63
+ export default { id, compile, interpret, simpleApplicator, plugin };
@@ -1,31 +1,17 @@
1
- import { Validation, canonicalUri } from "../experimental.js";
1
+ import { Validation } from "../experimental.js";
2
2
  import * as Instance from "../instance.js";
3
3
 
4
4
 
5
5
  const id = "https://json-schema.org/keyword/unevaluatedProperties";
6
6
 
7
- const compile = async (schema, ast, parentSchema) => {
8
- return [canonicalUri(parentSchema), await Validation.compile(schema, ast)];
9
- };
7
+ const compile = (schema, ast) => Validation.compile(schema, ast);
10
8
 
11
- const interpret = ([schemaUrl, unevaluatedProperties], instance, context) => {
9
+ const interpret = (unevaluatedProperties, instance, context) => {
12
10
  if (Instance.typeOf(instance) !== "object") {
13
11
  return true;
14
12
  }
15
13
 
16
- // Because order matters, we re-evaluate this schema skipping this keyword
17
- // just to collect all the evalauted properties.
18
- if (context.rootSchema === schemaUrl) {
19
- return true;
20
- }
21
- const evaluatedPropertiesPlugin = new EvaluatedPropertiesPlugin(schemaUrl);
22
- if (!Validation.interpret(schemaUrl, instance, {
23
- ...context,
24
- plugins: [...context.ast.plugins, evaluatedPropertiesPlugin]
25
- })) {
26
- return true;
27
- }
28
- const evaluatedProperties = evaluatedPropertiesPlugin.evaluatedProperties;
14
+ const evaluatedProperties = context.schemaEvaluatedProperties;
29
15
 
30
16
  let isValid = true;
31
17
  for (const [propertyNameNode, property] of Instance.entries(instance)) {
@@ -46,38 +32,32 @@ const interpret = ([schemaUrl, unevaluatedProperties], instance, context) => {
46
32
 
47
33
  const simpleApplicator = true;
48
34
 
49
- class EvaluatedPropertiesPlugin {
50
- constructor(rootSchema) {
51
- this.rootSchema = rootSchema;
52
- }
53
-
54
- beforeSchema(_url, _instance, context) {
35
+ const plugin = {
36
+ beforeSchema(_url, instance, context) {
55
37
  context.evaluatedProperties ??= new Set();
56
- context.schemaEvaluatedProperties ??= new Set();
57
- }
38
+ context.schemaEvaluatedProperties = new Set();
39
+ context.instanceLocation ??= Instance.uri(instance);
40
+ },
58
41
 
59
- beforeKeyword(_node, _instance, context) {
60
- context.rootSchema = this.rootSchema;
42
+ beforeKeyword(_node, instance, context, schemaContext) {
61
43
  context.evaluatedProperties = new Set();
62
- }
44
+ context.schemaEvaluatedProperties = schemaContext.schemaEvaluatedProperties;
45
+ context.instanceLocation = Instance.uri(instance);
46
+ },
63
47
 
64
- afterKeyword(_node, _instance, context, valid, schemaContext) {
65
- if (valid) {
66
- for (const property of context.evaluatedProperties) {
67
- schemaContext.schemaEvaluatedProperties.add(property);
68
- }
48
+ afterKeyword(_node, _instance, context, _valid, schemaContext) {
49
+ for (const property of context.evaluatedProperties) {
50
+ schemaContext.schemaEvaluatedProperties.add(property);
69
51
  }
70
- }
52
+ },
71
53
 
72
- afterSchema(_url, _instance, context, valid) {
73
- if (valid) {
54
+ afterSchema(_node, instance, context, valid) {
55
+ if (valid && Instance.uri(instance) === context.instanceLocation) {
74
56
  for (const property of context.schemaEvaluatedProperties) {
75
57
  context.evaluatedProperties.add(property);
76
58
  }
77
59
  }
78
-
79
- this.evaluatedProperties = context.evaluatedProperties;
80
60
  }
81
- }
61
+ };
82
62
 
83
- export default { id, compile, interpret, simpleApplicator };
63
+ export default { id, compile, interpret, simpleApplicator, plugin };
@@ -28,23 +28,36 @@ const compile = async (schema, ast) => {
28
28
  throw Error(`No schema found at '${url}'`);
29
29
  }
30
30
 
31
- ast[url] = typeof schemaValue === "boolean" ? schemaValue : await pipe(
32
- entries(schema),
33
- asyncMap(async ([keyword, keywordSchema]) => {
34
- const keywordHandler = getKeywordByName(keyword, schema.document.dialectId);
35
- if (keywordHandler.plugin) {
36
- ast.plugins.add(keywordHandler.plugin);
37
- }
38
- const keywordAst = await keywordHandler.compile(keywordSchema, ast, schema);
39
- return [keywordHandler.id, pointerAppend(keyword, canonicalUri(schema)), keywordAst];
40
- }),
41
- asyncCollectArray
42
- );
31
+ if (typeof schemaValue === "boolean") {
32
+ ast[url] = schemaValue;
33
+ } else {
34
+ ast[url] = await pipe(
35
+ entries(schema),
36
+ asyncMap(async ([keyword, keywordSchema]) => {
37
+ const keywordHandler = getKeywordByName(keyword, schema.document.dialectId);
38
+ if (keywordHandler.plugin) {
39
+ ast.plugins.add(keywordHandler.plugin);
40
+ }
41
+ const keywordAst = await keywordHandler.compile(keywordSchema, ast, schema);
42
+ return [keywordHandler.id, pointerAppend(keyword, canonicalUri(schema)), keywordAst];
43
+ }),
44
+ asyncCollectArray
45
+ );
46
+
47
+ // Keyword order shouldn't matter, but the unevaluated keywords are an exception :-(
48
+ ast[url].sort(keywordComparator);
49
+ }
43
50
  }
44
51
 
45
52
  return url;
46
53
  };
47
54
 
55
+ const lastKeywords = new Set([
56
+ "https://json-schema.org/keyword/unevaluatedProperties",
57
+ "https://json-schema.org/keyword/unevaluatedItems"
58
+ ]);
59
+ const keywordComparator = (_a, b) => lastKeywords.has(b[0]) ? -1 : 1;
60
+
48
61
  const interpret = (url, instance, context) => {
49
62
  let valid = true;
50
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperjump/json-schema",
3
- "version": "1.16.2",
3
+ "version": "1.16.4",
4
4
  "description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects",
5
5
  "type": "module",
6
6
  "main": "./stable/index.js",