json-schema-library 11.1.0 → 11.2.0

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.
Files changed (74) hide show
  1. package/.mocharc.js +1 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +21 -13
  4. package/bowtie/.editorconfig +21 -0
  5. package/bowtie/.prettierrc +6 -0
  6. package/bowtie/BOWTIE.md +54 -0
  7. package/bowtie/Dockerfile +6 -0
  8. package/bowtie/bowtie-api.ts +101 -0
  9. package/bowtie/bowtie.test.ts +76 -0
  10. package/bowtie/bowtie.ts +156 -0
  11. package/bowtie/package.json +11 -0
  12. package/bowtie/tsconfig.json +12 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +6 -514
  15. package/dist/index.d.mts +6 -514
  16. package/dist/index.mjs +1 -1
  17. package/dist/jlib.js +2 -13
  18. package/dist/remotes/index.cjs +1 -0
  19. package/dist/remotes/index.d.cts +7 -0
  20. package/dist/remotes/index.d.mts +7 -0
  21. package/dist/remotes/index.mjs +1 -0
  22. package/dist/types-B2wwNWyo.d.cts +513 -0
  23. package/dist/types-BhTU1l2h.d.mts +513 -0
  24. package/index.ts +0 -3
  25. package/package.json +14 -8
  26. package/src/Draft.ts +1 -1
  27. package/src/Keyword.ts +2 -3
  28. package/src/SchemaNode.ts +9 -0
  29. package/src/compileSchema.ts +4 -1
  30. package/src/draft04/keywords/$ref.ts +22 -14
  31. package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
  32. package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
  33. package/src/draft04/validateSchema.test.ts +20 -0
  34. package/src/draft06/keywords/$ref.ts +15 -5
  35. package/src/draft2019-09/keywords/$ref.test.ts +3 -1
  36. package/src/draft2019-09/keywords/$ref.ts +40 -16
  37. package/src/draft2019-09/keywords/additionalItems.ts +33 -10
  38. package/src/draft2019-09/keywords/items.ts +32 -10
  39. package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
  40. package/src/draft2019-09/methods/getData.ts +1 -1
  41. package/src/draft2019-09/validateSchema.test.ts +28 -0
  42. package/src/errors/errors.ts +4 -0
  43. package/src/formats/formats.ts +35 -28
  44. package/src/formats/hyperjump.d.ts +172 -0
  45. package/src/keywords/$ref.ts +47 -13
  46. package/src/keywords/properties.ts +1 -1
  47. package/src/keywords/propertyDependencies.ts +1 -1
  48. package/src/methods/getData.ts +1 -1
  49. package/src/validateNode.ts +4 -1
  50. package/tsconfig.json +11 -4
  51. package/tsconfig.test.json +9 -2
  52. package/tsdown.config.ts +1 -1
  53. package/Dockerfile +0 -6
  54. package/bowtie_jlib.js +0 -140
  55. package/remotes/draft04.json +0 -150
  56. package/remotes/draft06.json +0 -155
  57. package/remotes/draft07.json +0 -155
  58. package/remotes/draft2019-09.json +0 -42
  59. package/remotes/draft2019-09_meta_applicator.json +0 -53
  60. package/remotes/draft2019-09_meta_content.json +0 -14
  61. package/remotes/draft2019-09_meta_core.json +0 -54
  62. package/remotes/draft2019-09_meta_format.json +0 -11
  63. package/remotes/draft2019-09_meta_meta-data.json +0 -34
  64. package/remotes/draft2019-09_meta_validation.json +0 -95
  65. package/remotes/draft2020-12.json +0 -55
  66. package/remotes/draft2020-12_meta_applicator.json +0 -45
  67. package/remotes/draft2020-12_meta_content.json +0 -14
  68. package/remotes/draft2020-12_meta_core.json +0 -48
  69. package/remotes/draft2020-12_meta_format_annotation.json +0 -11
  70. package/remotes/draft2020-12_meta_format_assertion.json +0 -11
  71. package/remotes/draft2020-12_meta_meta_data.json +0 -34
  72. package/remotes/draft2020-12_meta_unevaluated.json +0 -12
  73. package/remotes/draft2020-12_meta_validation.json +0 -87
  74. package/remotes/index.ts +0 -48
@@ -4,7 +4,7 @@ import { isObject } from "../../utils/isObject";
4
4
  import { omit } from "../../utils/omit";
5
5
  import splitRef from "../../utils/splitRef";
6
6
  import { $refKeyword as draft06Keyword } from "../../draft06/keywords/$ref";
7
- import { SchemaNode } from "../../types";
7
+ import { isSchemaNode, JsonError, SchemaNode } from "../../types";
8
8
 
9
9
  export const $refKeyword: Keyword = {
10
10
  id: "$ref",
@@ -63,14 +63,9 @@ function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: string; pat
63
63
  return this;
64
64
  }
65
65
  const resolvedNode = getRef(this);
66
- // console.log("RESOLVE REF", node.schema, "resolved ref", node.$ref, "=>", resolvedNode.schema);
67
- if (resolvedNode != null) {
66
+ if (isSchemaNode(resolvedNode)) {
68
67
  path?.push({ pointer: pointer!, node: resolvedNode });
69
- // console.log("resolve ref", node.$ref, "=>", resolvedNode.schema, Object.keys(node.context.refs));
70
- } else {
71
- console.log("failed resolving", this.$ref, "from", Object.keys(this.context.refs));
72
68
  }
73
-
74
69
  return resolvedNode;
75
70
  }
76
71
 
@@ -81,7 +76,7 @@ function compileNext(referencedNode: SchemaNode, evaluationPath = referencedNode
81
76
  return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedSchema.schemaLocation);
82
77
  }
83
78
 
84
- function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
79
+ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError | undefined {
85
80
  if ($ref == null) {
86
81
  return node;
87
82
  }
@@ -100,8 +95,12 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
100
95
  // check for remote-host + pointer pair to switch rootSchema
101
96
  const fragments = splitRef($ref);
102
97
  if (fragments.length === 0) {
103
- // console.error("REF: INVALID", $ref);
104
- return undefined;
98
+ return node.createError("ref-error", {
99
+ ref: $ref,
100
+ pointer: node.evaluationPath,
101
+ schema: node.schema,
102
+ value: undefined
103
+ });
105
104
  }
106
105
 
107
106
  // resolve $ref as remote-host
@@ -111,8 +110,12 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
111
110
  if (node.context.remotes[$ref]) {
112
111
  return compileNext(node.context.remotes[$ref], node.evaluationPath);
113
112
  }
114
- // console.error("REF: UNFOUND 1", $ref, Object.keys(node.context.remotes));
115
- return undefined;
113
+ return node.createError("ref-error", {
114
+ ref: $ref,
115
+ pointer: node.evaluationPath,
116
+ schema: node.schema,
117
+ value: undefined
118
+ });
116
119
  }
117
120
 
118
121
  if (fragments.length === 2) {
@@ -142,9 +145,14 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
142
145
  }
143
146
  }
144
147
 
145
- // console.error("REF: UNFOUND 2", $ref);
148
+ // @todo returning error here breaks specs
146
149
  return undefined;
147
150
  }
148
151
 
149
- console.error("REF: INVALID", $ref);
152
+ return node.createError("ref-error", {
153
+ ref: $ref,
154
+ pointer: node.evaluationPath,
155
+ schema: node.schema,
156
+ value: undefined
157
+ });
150
158
  }
@@ -1,12 +1,26 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../../Keyword";
2
+ import { SchemaNode } from "../../SchemaNode";
2
3
 
3
4
  export const exclusiveMaximumKeyword: Keyword = {
4
5
  id: "exclusiveMaximum",
5
6
  keyword: "exclusiveMaximum",
7
+ parse,
6
8
  addValidate: ({ schema }) => schema.exclusiveMaximum === true || !isNaN(schema.maximum),
7
9
  validate: validateExclusiveMaximum
8
10
  };
9
11
 
12
+ function parse(node: SchemaNode) {
13
+ const { exclusiveMaximum } = node.schema;
14
+ if (exclusiveMaximum != null && !(typeof exclusiveMaximum === "number" || typeof exclusiveMaximum === "boolean")) {
15
+ return node.createError("schema-error", {
16
+ pointer: node.evaluationPath,
17
+ schema: node.schema,
18
+ value: undefined,
19
+ message: `Keyword 'exclusiveMaximum' must be a number - received '${typeof exclusiveMaximum}'`
20
+ });
21
+ }
22
+ }
23
+
10
24
  function validateExclusiveMaximum({ node, data, pointer }: JsonSchemaValidatorParams) {
11
25
  if (typeof data !== "number") {
12
26
  return undefined;
@@ -1,12 +1,26 @@
1
1
  import { Keyword, JsonSchemaValidatorParams } from "../../Keyword";
2
+ import { SchemaNode } from "../../SchemaNode";
2
3
 
3
4
  export const exclusiveMinimumKeyword: Keyword = {
4
5
  id: "exclusiveMinimum",
5
6
  keyword: "exclusiveMinimum",
7
+ parse,
6
8
  addValidate: ({ schema }) => schema.exclusiveMinimum === true || !isNaN(schema.minimum),
7
9
  validate: validateExclusiveMinimum
8
10
  };
9
11
 
12
+ function parse(node: SchemaNode) {
13
+ const { exclusiveMinimum } = node.schema;
14
+ if (exclusiveMinimum != null && !(typeof exclusiveMinimum === "number" || typeof exclusiveMinimum === "boolean")) {
15
+ return node.createError("schema-error", {
16
+ pointer: node.evaluationPath,
17
+ schema: node.schema,
18
+ value: undefined,
19
+ message: `Keyword 'exclusiveMinimum' must be a number - received '${typeof exclusiveMinimum}'`
20
+ });
21
+ }
22
+ }
23
+
10
24
  function validateExclusiveMinimum({ node, data, pointer }: JsonSchemaValidatorParams) {
11
25
  if (typeof data !== "number") {
12
26
  return undefined;
@@ -0,0 +1,20 @@
1
+ import { compileSchema as _compileSchema, type CompileOptions } from "../compileSchema";
2
+ import { strict as assert } from "assert";
3
+ import { draft04 } from "../draft04";
4
+ import { JsonSchema } from "../types";
5
+
6
+ const drafts = [draft04];
7
+ function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
8
+ return _compileSchema(schema, { ...options, drafts });
9
+ }
10
+
11
+ describe("validateSchema (4)", () => {
12
+ it("should error if `exclusiveMinimum` is not a number or boolean", () => {
13
+ const { schemaErrors } = compileSchema({ exclusiveMinimum: [] });
14
+ assert.equal(schemaErrors?.length, 1);
15
+ });
16
+ it("should error if `exclusiveMaximum` is not a number or boolean", () => {
17
+ const { schemaErrors } = compileSchema({ exclusiveMaximum: [] });
18
+ assert.equal(schemaErrors?.length, 1);
19
+ });
20
+ });
@@ -1,6 +1,6 @@
1
1
  import { Keyword, JsonSchemaValidatorParams, ValidationPath } from "../../Keyword";
2
2
  import { resolveRef } from "../../keywords/$ref";
3
- import { SchemaNode } from "../../types";
3
+ import { isSchemaNode, SchemaNode } from "../../types";
4
4
  import { resolveUri } from "../../utils/resolveUri";
5
5
  import { validateNode } from "../../validateNode";
6
6
 
@@ -51,16 +51,26 @@ function parseRef(node: SchemaNode) {
51
51
 
52
52
  function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
53
53
  const nextNode = resolveAllRefs(node, pointer, path);
54
- if (nextNode == null) {
55
- return undefined;
54
+ if (!isSchemaNode(nextNode)) {
55
+ return node.createError("ref-error", {
56
+ ref: node.schema.$ref,
57
+ pointer,
58
+ schema: node.schema,
59
+ value: data
60
+ });
56
61
  }
57
62
  return validateNode(nextNode, data, pointer, path);
58
63
  }
59
64
 
60
65
  function resolveAllRefs(node: SchemaNode, pointer: string, path: ValidationPath) {
61
66
  const nextNode = node.resolveRef({ pointer, path });
62
- if (nextNode == null) {
63
- return undefined;
67
+ if (!isSchemaNode(nextNode)) {
68
+ return node.createError("ref-error", {
69
+ ref: node.schema.$ref,
70
+ pointer,
71
+ schema: node.schema,
72
+ value: undefined
73
+ });
64
74
  }
65
75
  if (nextNode !== node && nextNode) {
66
76
  return resolveAllRefs(nextNode, pointer, path);
@@ -1,5 +1,6 @@
1
1
  import { strict as assert } from "assert";
2
2
  import { compileSchema } from "../../compileSchema";
3
+ import { isJsonError } from "../../types";
3
4
 
4
5
  describe("keyword : $ref : resolve", () => {
5
6
  it("should return undefined for missing reference", () => {
@@ -8,7 +9,8 @@ describe("keyword : $ref : resolve", () => {
8
9
  minLength: 1
9
10
  }).resolveRef();
10
11
 
11
- assert.deepEqual(node, undefined);
12
+ assert(isJsonError(node));
13
+ assert.deepEqual(node.code, "ref-error");
12
14
  });
13
15
 
14
16
  it("should resolve $ref from definitions", () => {
@@ -4,7 +4,7 @@ import splitRef from "../../utils/splitRef";
4
4
  import { omit } from "../../utils/omit";
5
5
  import { isObject } from "../../utils/isObject";
6
6
  import { validateNode } from "../../validateNode";
7
- import { SchemaNode } from "../../types";
7
+ import { isSchemaNode, JsonError, SchemaNode } from "../../types";
8
8
  import { get, split } from "@sagold/json-pointer";
9
9
  import { reduceRef } from "../../keywords/$ref";
10
10
 
@@ -73,7 +73,9 @@ export function parseRef(node: SchemaNode) {
73
73
  export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: string; path?: ValidationPath } = {}) {
74
74
  if (this.schema.$recursiveRef) {
75
75
  const nextNode = resolveRecursiveRef(this, path ?? []);
76
- path?.push({ pointer: pointer!, node: nextNode! });
76
+ if (isSchemaNode(nextNode)) {
77
+ path?.push({ pointer: pointer!, node: nextNode! });
78
+ }
77
79
  return nextNode;
78
80
  }
79
81
 
@@ -82,10 +84,8 @@ export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: stri
82
84
  }
83
85
 
84
86
  const resolvedNode = getRef(this);
85
- if (resolvedNode != null) {
87
+ if (isSchemaNode(resolvedNode)) {
86
88
  path?.push({ pointer: pointer!, node: resolvedNode });
87
- } else {
88
- // console.log("failed resolving", node.$ref, "from", Object.keys(node.context.refs));
89
89
  }
90
90
  return resolvedNode;
91
91
  }
@@ -93,13 +93,18 @@ export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: stri
93
93
  function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
94
94
  const nextNode = node.resolveRef({ pointer, path });
95
95
  if (nextNode != null) {
96
- // recursively resolveRef and validate
97
96
  return validateNode(nextNode, data, pointer, path);
98
97
  }
98
+ return node.createError("ref-error", {
99
+ ref: node.schema.$ref ?? node.schema.$recursiveRef,
100
+ pointer,
101
+ schema: node.schema,
102
+ value: data
103
+ });
99
104
  }
100
105
 
101
106
  // 1. https://json-schema.org/draft/2019-09/json-schema-core#scopes
102
- function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | undefined {
107
+ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | JsonError {
103
108
  const history = path;
104
109
 
105
110
  // RESTRICT BY CHANGE IN BASE-URL
@@ -135,7 +140,7 @@ function compileNext(referencedNode: SchemaNode, evaluationPath = referencedNode
135
140
  return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedNode.schemaLocation);
136
141
  }
137
142
 
138
- export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
143
+ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError {
139
144
  if ($ref == null) {
140
145
  return node;
141
146
  }
@@ -153,7 +158,12 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
153
158
  const fragments = splitRef($ref);
154
159
  if (fragments.length === 0) {
155
160
  // console.error("REF: INVALID", $ref);
156
- return undefined;
161
+ return node.createError("ref-error", {
162
+ ref: $ref,
163
+ pointer: node.evaluationPath,
164
+ schema: node.schema,
165
+ value: undefined
166
+ });
157
167
  }
158
168
 
159
169
  // resolve $ref as remote-host
@@ -175,7 +185,12 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
175
185
  }
176
186
  }
177
187
  // console.error("REF: UNFOUND 1", $ref);
178
- return undefined;
188
+ return node.createError("ref-error", {
189
+ ref: $ref,
190
+ pointer: node.evaluationPath,
191
+ schema: node.schema,
192
+ value: undefined
193
+ });
179
194
  }
180
195
 
181
196
  if (fragments.length === 2) {
@@ -206,16 +221,25 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
206
221
  // @ts-expect-error random path
207
222
  currentNode = currentNode[property];
208
223
  if (currentNode == null) {
209
- console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
210
- return undefined;
224
+ // console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
225
+ return node.createError("ref-error", {
226
+ ref: $ref,
227
+ pointer: node.evaluationPath,
228
+ schema: node.schema,
229
+ value: undefined,
230
+ host: fragments[0],
231
+ local: fragments[1]
232
+ });
211
233
  }
212
234
  }
213
235
  return currentNode;
214
236
  }
215
-
216
- // console.error("REF: UNFOUND 2", $ref);
217
- return undefined;
218
237
  }
219
238
 
220
- console.error("REF: UNHANDLED", $ref);
239
+ return node.createError("ref-error", {
240
+ ref: $ref,
241
+ pointer: node.evaluationPath,
242
+ schema: node.schema,
243
+ value: undefined
244
+ });
221
245
  }
@@ -4,28 +4,51 @@ import { SchemaNode } from "../../types";
4
4
  import { getValue } from "../../utils/getValue";
5
5
  import { validateNode } from "../../validateNode";
6
6
 
7
+ const KEYWORD = "additionalItems";
8
+
7
9
  export const additionalItemsKeyword: Keyword = {
8
- id: "additionalItems",
9
- keyword: "additionalItems",
10
+ id: KEYWORD,
11
+ keyword: KEYWORD,
10
12
  order: -10,
11
13
  parse: parseAdditionalItems,
12
14
  addResolve: (node: SchemaNode) => node.items != null,
13
15
  resolve: additionalItemsResolver,
14
- addValidate: ({ schema }) =>
15
- schema.additionalItems != null && schema.additionalItems !== true && Array.isArray(schema.items),
16
+ addValidate: ({ schema }) => schema[KEYWORD] != null && schema[KEYWORD] !== true && Array.isArray(schema.items),
16
17
  validate: validateAdditionalItems
17
18
  };
18
19
 
19
20
  // must come as last resolver
20
21
  export function parseAdditionalItems(node: SchemaNode) {
21
22
  const { schema, evaluationPath, schemaLocation } = node;
22
- if ((isObject(schema.additionalItems) || schema.additionalItems === true) && Array.isArray(schema.items)) {
23
- node.items = node.compileSchema(
24
- schema.additionalItems,
25
- `${evaluationPath}/additionalItems`,
26
- `${schemaLocation}/additionalItems`
27
- );
23
+ if (schema.additionalItems == null || schema.additionalItems === false) {
24
+ return;
25
+ }
26
+
27
+ const additionalItems = schema[KEYWORD];
28
+
29
+ if (!(isObject(additionalItems) || additionalItems === true)) {
30
+ return node.createError("schema-error", {
31
+ pointer: evaluationPath,
32
+ schema,
33
+ value: undefined,
34
+ message: `Keyword '${KEYWORD}' must be an object or a boolean - received '${typeof additionalItems}'`
35
+ });
36
+ }
37
+
38
+ if (!Array.isArray(schema.items)) {
39
+ return node.createAnnotation("schema-error", {
40
+ pointer: evaluationPath,
41
+ schema,
42
+ value: undefined,
43
+ message: `Keyword '${KEYWORD}' is only evaluated with a given items-array`
44
+ });
28
45
  }
46
+
47
+ node.items = node.compileSchema(
48
+ schema.additionalItems,
49
+ `${evaluationPath}/additionalItems`,
50
+ `${schemaLocation}/additionalItems`
51
+ );
29
52
  }
30
53
 
31
54
  function additionalItemsResolver({ node, key, data }: JsonSchemaResolverParams) {
@@ -3,11 +3,13 @@ import { SchemaNode } from "../../types";
3
3
  import { isObject } from "../../utils/isObject";
4
4
  import { validateNode } from "../../validateNode";
5
5
 
6
+ const KEYWORD = "items";
7
+
6
8
  export const itemsKeyword: Keyword = {
7
- id: "items",
8
- keyword: "items",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
9
11
  parse: parseItems,
10
- addResolve: (node) => (node.prefixItems || node.items) != null,
12
+ addResolve: (node) => (node.prefixItems || node[KEYWORD]) != null,
11
13
  resolve: itemsResolver,
12
14
  addValidate: ({ schema }) => schema.items != null,
13
15
  validate: validateItems
@@ -24,18 +26,38 @@ function itemsResolver({ node, key }: JsonSchemaResolverParams) {
24
26
 
25
27
  export function parseItems(node: SchemaNode) {
26
28
  const { schema, evaluationPath } = node;
27
- if (isObject(schema.items)) {
29
+ const items = schema[KEYWORD];
30
+ if (items == null || typeof items === "boolean") {
31
+ return;
32
+ }
33
+
34
+ if (isObject(items)) {
28
35
  const propertyNode = node.compileSchema(
29
- schema.items,
30
- `${evaluationPath}/items`,
31
- `${node.schemaLocation}/items`
36
+ items,
37
+ `${evaluationPath}/${KEYWORD}`,
38
+ `${node.schemaLocation}/${KEYWORD}`
32
39
  );
33
40
  node.items = propertyNode;
34
- } else if (Array.isArray(schema.items)) {
35
- node.prefixItems = schema.items.map((itemSchema, index) =>
36
- node.compileSchema(itemSchema, `${evaluationPath}/items/${index}`, `${node.schemaLocation}/items/${index}`)
41
+ return;
42
+ }
43
+
44
+ if (Array.isArray(items)) {
45
+ node.prefixItems = items.map((itemSchema, index) =>
46
+ node.compileSchema(
47
+ itemSchema,
48
+ `${evaluationPath}/${KEYWORD}/${index}`,
49
+ `${node.schemaLocation}/${KEYWORD}/${index}`
50
+ )
37
51
  );
52
+ return;
38
53
  }
54
+
55
+ return node.createError("schema-error", {
56
+ pointer: evaluationPath,
57
+ schema,
58
+ value: undefined,
59
+ message: `Keyword '${KEYWORD}' must be an object or array - received '${typeof items}'`
60
+ });
39
61
  }
40
62
 
41
63
  function validateItems({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
@@ -3,27 +3,40 @@ import { SchemaNode } from "../../types";
3
3
  import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../../Keyword";
4
4
  import { validateNode } from "../../validateNode";
5
5
 
6
+ const KEYWORD = "unevaluatedItems";
7
+
6
8
  /**
7
9
  * @draft >= 2019-09
8
10
  * Similar to additionalItems, but can "see" into subschemas and across references
9
11
  * https://json-schema.org/draft/2019-09/json-schema-core#rfc.section.9.3.1.3
10
12
  */
11
13
  export const unevaluatedItemsKeyword: Keyword = {
12
- id: "unevaluatedItems",
13
- keyword: "unevaluatedItems",
14
+ id: KEYWORD,
15
+ keyword: KEYWORD,
14
16
  parse: parseUnevaluatedItems,
15
- addValidate: ({ schema }) => schema.unevaluatedItems != null,
17
+ addValidate: ({ schema }) => schema[KEYWORD] != null,
16
18
  validate: validateUnevaluatedItems
17
19
  };
18
20
 
19
21
  export function parseUnevaluatedItems(node: SchemaNode) {
20
- if (!isObject(node.schema.unevaluatedItems)) {
22
+ const { unevaluatedItems } = node.schema;
23
+ if (unevaluatedItems == null || typeof unevaluatedItems === "boolean") {
21
24
  return;
22
25
  }
26
+
27
+ if (!isObject(unevaluatedItems)) {
28
+ return node.createError("schema-error", {
29
+ pointer: node.evaluationPath,
30
+ schema: node.schema,
31
+ value: undefined,
32
+ message: `Keyword '${KEYWORD}' must be an object - received '${typeof unevaluatedItems}'`
33
+ });
34
+ }
35
+
23
36
  node.unevaluatedItems = node.compileSchema(
24
37
  node.schema.unevaluatedItems,
25
- `${node.evaluationPath}/unevaluatedItems`,
26
- `${node.schemaLocation}/unevaluatedItems`
38
+ `${node.evaluationPath}/${KEYWORD}`,
39
+ `${node.schemaLocation}/${KEYWORD}`
27
40
  );
28
41
  }
29
42
 
@@ -160,7 +160,7 @@ export function getData(node: SchemaNode, data?: unknown, opts?: TemplateOptions
160
160
  return defaultData;
161
161
  }
162
162
 
163
- if (resolvedNode && resolvedNode !== currentNode) {
163
+ if (isSchemaNode(resolvedNode)) {
164
164
  defaultData = resolvedNode.getData(defaultData, opts) ?? defaultData;
165
165
  currentNode = resolvedNode;
166
166
  }
@@ -0,0 +1,28 @@
1
+ import { compileSchema as _compileSchema, type CompileOptions } from "../compileSchema";
2
+ import { strict as assert } from "assert";
3
+ import { draft2019 } from "../draft2019";
4
+ import { JsonSchema } from "../types";
5
+
6
+ const drafts = [draft2019];
7
+ function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
8
+ return _compileSchema(schema, { ...options, drafts });
9
+ }
10
+
11
+ describe("validateSchema (2019-09)", () => {
12
+ it("should error if `additionalItems` is of an invalid type", () => {
13
+ const { schemaErrors } = compileSchema({ additionalItems: [] });
14
+ assert.equal(schemaErrors?.length, 1);
15
+ });
16
+ it("should create annotation for `additionalItems` where items-array is missing", () => {
17
+ const { schemaAnnotations } = compileSchema({ additionalItems: true });
18
+ assert.equal(schemaAnnotations?.length, 1);
19
+ });
20
+ it("should error if `items` is of an invalid type", () => {
21
+ const { schemaErrors } = compileSchema({ items: 999 });
22
+ assert.equal(schemaErrors?.length, 1);
23
+ });
24
+ it("should error if `unevaluatedItems` is not an object or boolean", () => {
25
+ const { schemaErrors } = compileSchema({ unevaluatedItems: [] });
26
+ assert.equal(schemaErrors?.length, 1);
27
+ });
28
+ });
@@ -19,12 +19,15 @@ export const errors = {
19
19
  "format-duration-error": "Value `{{value}}` at `{{pointer}}` is not a valid duration",
20
20
  "format-email-error": "Value `{{value}}` at `{{pointer}}` is not a valid email",
21
21
  "format-hostname-error": "Value `{{value}}` at `{{pointer}}` is not a valid hostname",
22
+ "format-idn-hostname-error": "Value `{{value}}` at `{{pointer}}` is not a valid idn hostname",
22
23
  "format-ipv4-error": "Value `{{value}}` at `{{pointer}}` is not a valid IPv4 address",
23
24
  "format-ipv4-leading-zero-error":
24
25
  "IPv4 addresses starting with zero are invalid, since they are interpreted as octals",
25
26
  "format-ipv6-error": "Value `{{value}}` at `{{pointer}}` is not a valid IPv6 address",
26
27
  "format-ipv6-leading-zero-error":
27
28
  "IPv6 addresses starting with zero are invalid, since they are interpreted as octals",
29
+ "format-iri-error": "Value `{{value}}` at `{{pointer}}` is not a valid iri",
30
+ "format-iri-reference-error": "Value `{{value}}` at `{{pointer}}` is not a valid iri-reference",
28
31
  "format-json-pointer-error": "Value `{{value}}` at `{{pointer}}` is not a valid json-pointer",
29
32
  "format-regex-error": "Value `{{value}}` at `{{pointer}}` is not a valid regular expression",
30
33
  "format-time-error": "Value `{{value}}` at `{{pointer}}` is not a valid time",
@@ -62,6 +65,7 @@ export const errors = {
62
65
  "pattern-error": "Value in `{{pointer}}` should match `{{description}}`, but received `{{received}}`",
63
66
  "pattern-properties-error":
64
67
  "Property `{{key}}` does not match any patterns in `{{pointer}}`. Valid patterns are: {{patterns}}",
68
+ "ref-error": "Could not resolve $ref '{{ref}}' from '{{pointer}}'",
65
69
  "required-property-error": "The required property `{{key}}` is missing at `{{pointer}}`",
66
70
  /** return schema-warning with createSchemaWarning:true when a valid, but undefined property was found */
67
71
  "schema-warning": "Failed retrieving a schema from '{{pointer}}' to key '{{key}}'",