json-schema-library 11.0.5 → 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 (113) hide show
  1. package/.mocharc.js +1 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +120 -0
  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 +39 -470
  15. package/dist/index.d.mts +39 -470
  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 +10 -4
  25. package/package.json +14 -8
  26. package/src/Keyword.ts +37 -12
  27. package/src/SchemaNode.ts +84 -16
  28. package/src/compileSchema.ts +56 -4
  29. package/src/draft04/keywords/$ref.ts +22 -14
  30. package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
  31. package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
  32. package/src/draft04/validateSchema.test.ts +20 -0
  33. package/src/draft06/keywords/$ref.ts +15 -5
  34. package/src/draft2019-09/keywords/$ref.test.ts +3 -1
  35. package/src/draft2019-09/keywords/$ref.ts +40 -16
  36. package/src/draft2019-09/keywords/additionalItems.ts +33 -10
  37. package/src/draft2019-09/keywords/items.ts +32 -10
  38. package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
  39. package/src/draft2019-09/methods/getData.ts +1 -1
  40. package/src/draft2019-09/validateSchema.test.ts +28 -0
  41. package/src/errors/errors.ts +8 -1
  42. package/src/formats/formats.ts +35 -28
  43. package/src/formats/hyperjump.d.ts +172 -0
  44. package/src/keywords/$defs.ts +34 -8
  45. package/src/keywords/$ref.ts +59 -13
  46. package/src/keywords/additionalProperties.ts +19 -8
  47. package/src/keywords/allOf.ts +44 -18
  48. package/src/keywords/anyOf.ts +38 -19
  49. package/src/keywords/contains.ts +21 -9
  50. package/src/keywords/dependencies.ts +37 -17
  51. package/src/keywords/dependentRequired.ts +56 -38
  52. package/src/keywords/dependentSchemas.ts +37 -13
  53. package/src/keywords/deprecated.ts +32 -8
  54. package/src/keywords/enum.ts +30 -8
  55. package/src/keywords/exclusiveMaximum.ts +21 -2
  56. package/src/keywords/exclusiveMinimum.ts +22 -3
  57. package/src/keywords/format.ts +21 -2
  58. package/src/keywords/ifthenelse.ts +49 -5
  59. package/src/keywords/items.ts +27 -13
  60. package/src/keywords/maxItems.ts +22 -2
  61. package/src/keywords/maxLength.ts +30 -9
  62. package/src/keywords/maxProperties.ts +30 -9
  63. package/src/keywords/maximum.ts +28 -8
  64. package/src/keywords/minItems.ts +30 -9
  65. package/src/keywords/minLength.ts +30 -9
  66. package/src/keywords/minProperties.ts +26 -5
  67. package/src/keywords/minimum.ts +32 -13
  68. package/src/keywords/multipleOf.ts +33 -12
  69. package/src/keywords/not.ts +23 -10
  70. package/src/keywords/oneOf.ts +29 -9
  71. package/src/keywords/pattern.ts +35 -9
  72. package/src/keywords/properties.ts +34 -11
  73. package/src/keywords/propertyDependencies.test.ts +180 -0
  74. package/src/keywords/propertyDependencies.ts +173 -0
  75. package/src/keywords/propertyNames.ts +26 -14
  76. package/src/keywords/required.ts +31 -8
  77. package/src/keywords/type.ts +53 -16
  78. package/src/keywords/unevaluatedItems.ts +24 -8
  79. package/src/keywords/unevaluatedProperties.ts +24 -7
  80. package/src/keywords/uniqueItems.ts +23 -4
  81. package/src/mergeNode.ts +9 -4
  82. package/src/methods/getData.ts +1 -1
  83. package/src/settings.ts +2 -1
  84. package/src/types.ts +1 -1
  85. package/src/utils/isListOfStrings.ts +3 -0
  86. package/src/validate.test.ts +0 -2
  87. package/src/validateNode.ts +6 -3
  88. package/src/validateSchema.test.ts +312 -0
  89. package/tsconfig.json +11 -4
  90. package/tsconfig.test.json +9 -2
  91. package/tsdown.config.ts +1 -1
  92. package/Dockerfile +0 -6
  93. package/bowtie_jlib.js +0 -140
  94. package/remotes/draft04.json +0 -150
  95. package/remotes/draft06.json +0 -155
  96. package/remotes/draft07.json +0 -155
  97. package/remotes/draft2019-09.json +0 -42
  98. package/remotes/draft2019-09_meta_applicator.json +0 -53
  99. package/remotes/draft2019-09_meta_content.json +0 -14
  100. package/remotes/draft2019-09_meta_core.json +0 -54
  101. package/remotes/draft2019-09_meta_format.json +0 -11
  102. package/remotes/draft2019-09_meta_meta-data.json +0 -34
  103. package/remotes/draft2019-09_meta_validation.json +0 -95
  104. package/remotes/draft2020-12.json +0 -55
  105. package/remotes/draft2020-12_meta_applicator.json +0 -45
  106. package/remotes/draft2020-12_meta_content.json +0 -14
  107. package/remotes/draft2020-12_meta_core.json +0 -48
  108. package/remotes/draft2020-12_meta_format_annotation.json +0 -11
  109. package/remotes/draft2020-12_meta_format_assertion.json +0 -11
  110. package/remotes/draft2020-12_meta_meta_data.json +0 -34
  111. package/remotes/draft2020-12_meta_unevaluated.json +0 -12
  112. package/remotes/draft2020-12_meta_validation.json +0 -87
  113. package/remotes/index.ts +0 -48
@@ -1,4 +1,4 @@
1
- import { SchemaNode } from "../types";
1
+ import { isJsonError, isSchemaNode, JsonError, SchemaNode } from "../types";
2
2
  import { Keyword, JsonSchemaValidatorParams, ValidationPath, JsonSchemaReducerParams } from "../Keyword";
3
3
  import { resolveUri } from "../utils/resolveUri";
4
4
  import splitRef from "../utils/splitRef";
@@ -72,6 +72,18 @@ export function parseRef(node: SchemaNode) {
72
72
  node.$ref = `#${node.$ref}`;
73
73
  }
74
74
  }
75
+
76
+ // validate simple ref to definitions
77
+ if (node.$ref?.startsWith("#/$defs/")) {
78
+ if (get(node.getNodeRoot().schema, node.$ref) == null) {
79
+ return node.createError("schema-error", {
80
+ pointer: `${node.schemaLocation}/$ref`,
81
+ schema: node.schema,
82
+ value: node.schema.$ref,
83
+ message: `Invalid $ref to missing target '${node.schema.ref}'`
84
+ });
85
+ }
86
+ }
75
87
  }
76
88
 
77
89
  export function reduceRef({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
@@ -81,7 +93,12 @@ export function reduceRef({ node, data, key, pointer, path }: JsonSchemaReducerP
81
93
 
82
94
  const resolvedNode = node.resolveRef({ pointer, path });
83
95
  if (resolvedNode == null) {
84
- return;
96
+ return node.createError("ref-error", {
97
+ ref: node.schema.$ref ?? node.schema.$dynamicRef,
98
+ pointer,
99
+ schema: node.schema,
100
+ value: data
101
+ });
85
102
  }
86
103
 
87
104
  if (resolvedNode.schemaLocation === node.schemaLocation) {
@@ -95,6 +112,9 @@ export function reduceRef({ node, data, key, pointer, path }: JsonSchemaReducerP
95
112
  export function resolveRef(this: SchemaNode, { pointer, path = [] }: { pointer?: string; path?: ValidationPath } = {}) {
96
113
  if (this.schema.$dynamicRef) {
97
114
  const nextNode = resolveRecursiveRef(this, path);
115
+ if (isJsonError(nextNode)) {
116
+ return nextNode;
117
+ }
98
118
  path.push({ pointer: pointer!, node: nextNode! });
99
119
  return nextNode;
100
120
  }
@@ -104,7 +124,7 @@ export function resolveRef(this: SchemaNode, { pointer, path = [] }: { pointer?:
104
124
  }
105
125
 
106
126
  const resolvedNode = getRef(this);
107
- if (resolvedNode != null) {
127
+ if (isSchemaNode(resolvedNode)) {
108
128
  path.push({ pointer: pointer!, node: resolvedNode });
109
129
  }
110
130
 
@@ -117,10 +137,16 @@ function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorPar
117
137
  // recursively resolveRef and validate
118
138
  return validateNode(nextNode, data, pointer, path);
119
139
  }
140
+ return node.createError("ref-error", {
141
+ ref: node.schema.$ref ?? node.schema.$dynamicRef,
142
+ pointer,
143
+ schema: node.schema,
144
+ value: data
145
+ });
120
146
  }
121
147
 
122
148
  // 1. https://json-schema.org/draft/2019-09/json-schema-core#scopes
123
- function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | undefined {
149
+ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | JsonError {
124
150
  const history = path;
125
151
  const refInCurrentScope = resolveUri(node.$id, node.schema.$dynamicRef);
126
152
 
@@ -168,7 +194,7 @@ function compileNext(referencedNode: SchemaNode, sourceNode: SchemaNode) {
168
194
  );
169
195
  }
170
196
 
171
- export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
197
+ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError {
172
198
  if ($ref == null) {
173
199
  return node;
174
200
  }
@@ -190,7 +216,12 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
190
216
  // check for remote-host + pointer pair to switch rootSchema
191
217
  const fragments = splitRef($ref);
192
218
  if (fragments.length === 0) {
193
- return undefined;
219
+ return node.createError("ref-error", {
220
+ ref: $ref,
221
+ pointer: node.evaluationPath,
222
+ schema: node.schema,
223
+ value: undefined
224
+ });
194
225
  }
195
226
 
196
227
  // resolve $ref as remote-host
@@ -200,6 +231,7 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
200
231
  if (node.context.remotes[$ref]) {
201
232
  return compileNext(node.context.remotes[$ref], node);
202
233
  }
234
+
203
235
  if ($ref[0] === "#") {
204
236
  // support refOfUnknownKeyword
205
237
  const rootSchema = node.context.rootNode.schema;
@@ -209,7 +241,12 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
209
241
  }
210
242
  }
211
243
  // console.error("REF: UNFOUND 1", $ref);
212
- return undefined;
244
+ return node.createError("ref-error", {
245
+ ref: $ref,
246
+ pointer: node.evaluationPath,
247
+ schema: node.schema,
248
+ value: undefined
249
+ });
213
250
  }
214
251
 
215
252
  if (fragments.length === 2) {
@@ -240,16 +277,25 @@ export function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefi
240
277
  // @ts-expect-error random path
241
278
  currentNode = currentNode[property];
242
279
  if (currentNode == null) {
243
- console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
244
- return undefined;
280
+ // console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
281
+ return node.createError("ref-error", {
282
+ ref: $ref,
283
+ pointer: node.evaluationPath,
284
+ schema: node.schema,
285
+ value: undefined,
286
+ host: fragments[0],
287
+ local: fragments[1]
288
+ });
245
289
  }
246
290
  }
247
291
  return currentNode;
248
292
  }
249
-
250
- console.error("REF: UNFOUND 2", $ref);
251
- return undefined;
252
293
  }
253
294
 
254
- console.error("REF: UNHANDLED", $ref);
295
+ return node.createError("ref-error", {
296
+ ref: $ref,
297
+ pointer: node.evaluationPath,
298
+ schema: node.schema,
299
+ value: undefined
300
+ });
255
301
  }
@@ -1,7 +1,7 @@
1
1
  import settings from "../settings";
2
2
  import { isObject } from "../utils/isObject";
3
- import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType} from "../Keyword";
4
- import { SchemaNode } from "../types";
3
+ import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
4
+ import { isBooleanSchema, SchemaNode } from "../types";
5
5
  import { getValue } from "../utils/getValue";
6
6
  import { validateNode } from "../validateNode";
7
7
 
@@ -24,13 +24,24 @@ export const additionalPropertiesKeyword: Keyword = {
24
24
  // must come as last resolver
25
25
  export function parseAdditionalProperties(node: SchemaNode) {
26
26
  const { schema, evaluationPath, schemaLocation } = node;
27
- if (isObject(schema.additionalProperties)) {
28
- node.additionalProperties = node.compileSchema(
29
- schema.additionalProperties,
30
- `${evaluationPath}/additionalProperties`,
31
- `${schemaLocation}/additionalProperties`
32
- );
27
+ if (schema.additionalProperties == null || isBooleanSchema(schema.additionalProperties)) {
28
+ return;
33
29
  }
30
+
31
+ if (!isObject(schema.additionalProperties)) {
32
+ return node.createError("schema-error", {
33
+ pointer: node.schemaLocation,
34
+ schema,
35
+ value: schema.additionalProperties,
36
+ message: `keyword 'additionalProperties' must be a valid JSON Schema - receoved: ${typeof schema.additionalProperties}`
37
+ });
38
+ }
39
+
40
+ node.additionalProperties = node.compileSchema(
41
+ schema.additionalProperties,
42
+ `${evaluationPath}/additionalProperties`,
43
+ `${schemaLocation}/additionalProperties`
44
+ );
34
45
  }
35
46
 
36
47
  function additionalPropertyResolver({ node, data, key }: JsonSchemaResolverParams) {
@@ -1,29 +1,55 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
2
+ import {
3
+ Keyword,
4
+ JsonSchemaReducerParams,
5
+ JsonSchemaValidatorParams,
6
+ ValidationReturnType,
7
+ ValidationAnnotation
8
+ } from "../Keyword";
3
9
  import { SchemaNode } from "../types";
4
10
  import { validateNode } from "../validateNode";
5
11
 
12
+ const KEYWORD = "allOf";
13
+
6
14
  export const allOfKeyword: Keyword = {
7
- id: "allOf",
8
- keyword: "allOf",
15
+ id: KEYWORD,
16
+ keyword: KEYWORD,
9
17
  parse: parseAllOf,
10
- addReduce: (node: SchemaNode) => node.allOf != null,
18
+ addReduce: (node: SchemaNode) => node[KEYWORD] != null,
11
19
  reduce: reduceAllOf,
12
- addValidate: (node) => node.allOf != null,
20
+ addValidate: (node) => node[KEYWORD] != null,
13
21
  validate: validateAllOf
14
22
  };
15
23
 
16
24
  export function parseAllOf(node: SchemaNode) {
17
- const { schema, evaluationPath } = node;
18
- if (Array.isArray(schema.allOf) && schema.allOf.length) {
19
- node.allOf = schema.allOf.map((s, index) =>
20
- node.compileSchema(s, `${evaluationPath}/allOf/${index}`, `${node.schemaLocation}/allOf/${index}`)
21
- );
25
+ const { schema, evaluationPath, schemaLocation } = node;
26
+ if (schema[KEYWORD] == null) {
27
+ return;
28
+ }
29
+ if (!Array.isArray(schema[KEYWORD])) {
30
+ return node.createError("schema-error", {
31
+ pointer: schemaLocation,
32
+ schema,
33
+ value: schema[KEYWORD],
34
+ message: `Keyword '${KEYWORD}' must be an array - received '${typeof schema[KEYWORD]}'`
35
+ });
36
+ }
37
+ if (schema[KEYWORD].length === 0) {
38
+ return;
22
39
  }
40
+
41
+ node[KEYWORD] = schema[KEYWORD].map((s, index) =>
42
+ node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
43
+ );
44
+
45
+ return node[KEYWORD].reduce((errors, node) => {
46
+ if (node.schemaValidation) errors.push(...node.schemaValidation);
47
+ return errors;
48
+ }, [] as ValidationAnnotation[]);
23
49
  }
24
50
 
25
51
  function reduceAllOf({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
26
- if (node.allOf == null) {
52
+ if (node[KEYWORD] == null) {
27
53
  return;
28
54
  }
29
55
 
@@ -31,15 +57,15 @@ function reduceAllOf({ node, data, key, pointer, path }: JsonSchemaReducerParams
31
57
  // dynamic schema parts
32
58
  let mergedSchema = {};
33
59
  let dynamicId = "";
34
- for (let i = 0; i < node.allOf.length; i += 1) {
35
- const { node: schemaNode } = node.allOf[i].reduceNode(data, { key, pointer, path });
60
+ for (let i = 0; i < node[KEYWORD].length; i += 1) {
61
+ const { node: schemaNode } = node[KEYWORD][i].reduceNode(data, { key, pointer, path });
36
62
  if (schemaNode) {
37
63
  const nestedDynamicId = schemaNode.dynamicId?.replace(node.dynamicId, "") ?? "";
38
- const localDynamicId = nestedDynamicId === "" ? `allOf/${i}` : nestedDynamicId;
64
+ const localDynamicId = nestedDynamicId === "" ? `${KEYWORD}/${i}` : nestedDynamicId;
39
65
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
40
66
 
41
- const schema = mergeSchema(node.allOf[i].schema, schemaNode.schema);
42
- mergedSchema = mergeSchema(mergedSchema, schema, "allOf", "contains");
67
+ const schema = mergeSchema(node[KEYWORD][i].schema, schemaNode.schema);
68
+ mergedSchema = mergeSchema(mergedSchema, schema, KEYWORD, "contains");
43
69
  }
44
70
  }
45
71
 
@@ -52,11 +78,11 @@ function reduceAllOf({ node, data, key, pointer, path }: JsonSchemaReducerParams
52
78
  }
53
79
 
54
80
  function validateAllOf({ node, data, pointer, path }: JsonSchemaValidatorParams) {
55
- if (!Array.isArray(node.allOf) || node.allOf.length === 0) {
81
+ if (!Array.isArray(node[KEYWORD]) || node[KEYWORD].length === 0) {
56
82
  return;
57
83
  }
58
84
  const errors: ValidationReturnType = [];
59
- node.allOf.forEach((allOfNode) => {
85
+ node[KEYWORD].forEach((allOfNode) => {
60
86
  errors.push(...validateNode(allOfNode, data, pointer, path));
61
87
  });
62
88
  return errors;
@@ -1,45 +1,64 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams } from "../Keyword";
2
+ import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { SchemaNode } from "../types";
4
4
  import { validateNode } from "../validateNode";
5
5
 
6
+ const KEYWORD = "anyOf";
7
+
6
8
  export const anyOfKeyword: Keyword = {
7
- id: "anyOf",
8
- keyword: "anyOf",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
9
11
  parse: parseAnyOf,
10
- addReduce: (node) => node.anyOf != null,
12
+ addReduce: (node) => node[KEYWORD] != null,
11
13
  reduce: reduceAnyOf,
12
- addValidate: (node) => node.anyOf != null,
14
+ addValidate: (node) => node[KEYWORD] != null,
13
15
  validate: validateAnyOf
14
16
  };
15
17
 
16
18
  export function parseAnyOf(node: SchemaNode) {
17
19
  const { schema, evaluationPath, schemaLocation } = node;
18
- if (Array.isArray(schema.anyOf) && schema.anyOf.length) {
19
- node.anyOf = schema.anyOf.map((s, index) =>
20
- node.compileSchema(s, `${evaluationPath}/anyOf/${index}`, `${schemaLocation}/anyOf/${index}`)
21
- );
20
+ if (schema[KEYWORD] == null) {
21
+ return;
22
22
  }
23
+ if (!Array.isArray(schema[KEYWORD])) {
24
+ return node.createError("schema-error", {
25
+ pointer: schemaLocation,
26
+ schema,
27
+ value: schema[KEYWORD],
28
+ message: `Keyword '${KEYWORD}' must be an array - received '${typeof schema[KEYWORD]}'`
29
+ });
30
+ }
31
+ if (schema[KEYWORD].length === 0) {
32
+ return;
33
+ }
34
+ node[KEYWORD] = schema[KEYWORD].map((s, index) =>
35
+ node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
36
+ );
37
+
38
+ return node[KEYWORD].reduce((errors, node) => {
39
+ if (node.schemaValidation) errors.push(...node.schemaValidation);
40
+ return errors;
41
+ }, [] as ValidationAnnotation[]);
23
42
  }
24
43
 
25
44
  function reduceAnyOf({ node, data, pointer, path }: JsonSchemaReducerParams) {
26
- if (node.anyOf == null) {
45
+ if (node[KEYWORD] == null) {
27
46
  return;
28
47
  }
29
48
 
30
49
  let mergedSchema = {};
31
50
  let dynamicId = "";
32
- for (let i = 0; i < node.anyOf.length; i += 1) {
33
- if (validateNode(node.anyOf[i], data, pointer, path).length === 0) {
34
- const { node: schemaNode } = node.anyOf[i].reduceNode(data);
51
+ for (let i = 0; i < node[KEYWORD].length; i += 1) {
52
+ if (validateNode(node[KEYWORD][i], data, pointer, path).length === 0) {
53
+ const { node: schemaNode } = node[KEYWORD][i].reduceNode(data);
35
54
 
36
55
  if (schemaNode) {
37
56
  const nestedDynamicId = schemaNode.dynamicId?.replace(node.dynamicId, "") ?? "";
38
- const localDynamicId = nestedDynamicId === "" ? `anyOf/${i}` : nestedDynamicId;
57
+ const localDynamicId = nestedDynamicId === "" ? `${KEYWORD}/${i}` : nestedDynamicId;
39
58
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
40
59
 
41
- const schema = mergeSchema(node.anyOf[i].schema, schemaNode.schema);
42
- mergedSchema = mergeSchema(mergedSchema, schema, "anyOf");
60
+ const schema = mergeSchema(node[KEYWORD][i].schema, schemaNode.schema);
61
+ mergedSchema = mergeSchema(mergedSchema, schema, KEYWORD);
43
62
  }
44
63
  }
45
64
  }
@@ -52,13 +71,13 @@ function reduceAnyOf({ node, data, pointer, path }: JsonSchemaReducerParams) {
52
71
  }
53
72
 
54
73
  function validateAnyOf({ node, data, pointer, path }: JsonSchemaValidatorParams) {
55
- if (node.anyOf == null) {
74
+ if (node[KEYWORD] == null) {
56
75
  return;
57
76
  }
58
- for (const anyOf of node.anyOf) {
77
+ for (const anyOf of node[KEYWORD]) {
59
78
  if (validateNode(anyOf, data, pointer, path).length === 0) {
60
79
  return undefined;
61
80
  }
62
81
  }
63
- return node.createError("any-of-error", { pointer, schema: node.schema, value: data, anyOf: node.schema.anyOf });
82
+ return node.createError("any-of-error", { pointer, schema: node.schema, value: data, anyOf: node.schema[KEYWORD] });
64
83
  }
@@ -1,20 +1,22 @@
1
1
  import { isObject } from "../utils/isObject";
2
- import { SchemaNode } from "../types";
2
+ import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
3
3
  import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
4
4
  import { validateNode } from "../validateNode";
5
5
 
6
+ const KEYWORD = "contains";
7
+
6
8
  export const containsKeyword: Keyword = {
7
- id: "contains",
8
- keyword: "contains",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
9
11
  parse: parseContains,
10
- addValidate: (node) => node.contains != null,
12
+ addValidate: (node) => node[KEYWORD] != null,
11
13
  validate: validateContains,
12
- addReduce: (node) => node.contains != null,
14
+ addReduce: (node) => node[KEYWORD] != null,
13
15
  reduce: ({ node }) => {
14
16
  return node.compileSchema(
15
17
  {
16
18
  items: {
17
- anyOf: [node.contains!.schema] // we tested for contains in addReduce
19
+ anyOf: [node[KEYWORD]!.schema] // we tested for contains in addReduce
18
20
  }
19
21
  },
20
22
  node.evaluationPath,
@@ -24,11 +26,21 @@ export const containsKeyword: Keyword = {
24
26
  };
25
27
 
26
28
  export function parseContains(node: SchemaNode) {
27
- const { schema, evaluationPath } = node;
28
- if (schema.contains == null) {
29
+ const contains = node.schema[KEYWORD];
30
+ if (contains == null) {
29
31
  return;
30
32
  }
31
- node.contains = node.compileSchema(schema.contains, `${evaluationPath}/contains`);
33
+ if (!(isJsonSchema(contains) || isBooleanSchema(contains))) {
34
+ return node.createError("schema-error", {
35
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
36
+ schema: node.schema,
37
+ value: contains,
38
+ message: `Keyword '${KEYWORD}' must be a valid JSON Schema - received '${typeof contains}'`
39
+ });
40
+ }
41
+
42
+ node[KEYWORD] = node.compileSchema(contains, `${node.evaluationPath}/${KEYWORD}`);
43
+ return node[KEYWORD].schemaValidation;
32
44
  }
33
45
 
34
46
  function validateContains({ node, data, pointer, path }: JsonSchemaValidatorParams) {
@@ -1,4 +1,4 @@
1
- import { isSchemaNode, SchemaNode } from "../types";
1
+ import { isBooleanSchema, isJsonSchema, isSchemaNode, SchemaNode } from "../types";
2
2
  import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { isObject } from "../utils/isObject";
4
4
  import { mergeNode } from "../mergeNode";
@@ -6,38 +6,58 @@ import { hasProperty } from "../utils/hasProperty";
6
6
  import { validateDependentRequired } from "./dependentRequired";
7
7
  import { validateDependentSchemas } from "./dependentSchemas";
8
8
  import sanitizeErrors from "../utils/sanitizeErrors";
9
+ import { isListOfStrings } from "../utils/isListOfStrings";
10
+
11
+ const KEYWORD = "dependencies";
9
12
 
10
13
  export const dependenciesKeyword: Keyword = {
11
- id: "dependencies",
12
- keyword: "dependencies",
14
+ id: KEYWORD,
15
+ keyword: KEYWORD,
13
16
  parse: parseDependencies,
14
17
  order: -9,
15
- addReduce: (node) => node.schema.dependencies != null,
18
+ addReduce: (node) => node.schema[KEYWORD] != null, // because we remap this has to be tested on schema
16
19
  reduce: reduceDependencies,
17
- addValidate: (node) => node.schema.dependencies != null,
20
+ addValidate: (node) => node.schema[KEYWORD] != null, // because we remap this has to be tested on schema
18
21
  validate: validateDependencies
19
22
  };
20
23
 
21
24
  export function parseDependencies(node: SchemaNode) {
22
25
  const { dependencies } = node.schema;
23
26
  if (!isObject(dependencies)) {
24
- return;
27
+ return node.createError("schema-error", {
28
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
29
+ schema: node.schema,
30
+ value: dependencies,
31
+ message: `Keyword '${KEYWORD}' must be an object - received ${typeof dependencies}`
32
+ });
25
33
  }
26
- const schemas = Object.keys(dependencies);
27
- schemas.forEach((property) => {
34
+
35
+ const errors: ValidationAnnotation[] = [];
36
+ for (const property of Object.keys(dependencies)) {
28
37
  const schema = dependencies[property] as string[];
29
- if (isObject(schema) || typeof schema === "boolean") {
38
+ if (isJsonSchema(schema) || isBooleanSchema(schema)) {
30
39
  node.dependentSchemas = node.dependentSchemas ?? {};
31
40
  node.dependentSchemas[property] = node.compileSchema(
32
41
  schema,
33
- `${node.evaluationPath}/dependencies/${property}`,
34
- `${node.schemaLocation}/dependencies/${property}`
42
+ `${node.evaluationPath}/${KEYWORD}/${property}`,
43
+ `${node.schemaLocation}/${KEYWORD}/${property}`
35
44
  );
36
- } else {
45
+ } else if (isListOfStrings(schema)) {
37
46
  node.dependentRequired = node.dependentRequired ?? {};
38
47
  node.dependentRequired[property] = schema;
48
+ } else {
49
+ errors.push(
50
+ node.createError("schema-error", {
51
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
52
+ schema: node.schema,
53
+ value: dependencies,
54
+ message: `Keyword '${KEYWORD}[string]' must be JSON Schema or string[]`
55
+ })
56
+ );
39
57
  }
40
- });
58
+ }
59
+
60
+ return errors;
41
61
  }
42
62
 
43
63
  export function reduceDependencies({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
@@ -66,7 +86,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
66
86
  required.push(...dependentRequired[propertyName]);
67
87
 
68
88
  // @dynamicId
69
- const localDynamicId = `dependencies/${propertyName}`;
89
+ const localDynamicId = `${KEYWORD}/${propertyName}`;
70
90
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
71
91
  });
72
92
  }
@@ -90,7 +110,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
90
110
  // but probably not how json-schema spec defines this behaviour (resolve only within sub-schema)
91
111
  const reducedDependency = { ...dependency, schema: { ...dependency.schema, required } }.reduceNode(data, {
92
112
  key,
93
- pointer: `${pointer}/dependencies/${propertyName}`,
113
+ pointer: `${pointer}/${KEYWORD}/${propertyName}`,
94
114
  path
95
115
  }).node as SchemaNode;
96
116
 
@@ -98,7 +118,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
98
118
 
99
119
  // @dynamicId
100
120
  const nestedDynamicId = reducedDependency.dynamicId?.replace(node.dynamicId, "") ?? "";
101
- const localDynamicId = nestedDynamicId === "" ? `dependencies/${propertyName}` : nestedDynamicId;
121
+ const localDynamicId = nestedDynamicId === "" ? `${KEYWORD}/${propertyName}` : nestedDynamicId;
102
122
  dynamicId += `${dynamicId === "" ? "" : ","}${localDynamicId}`;
103
123
  });
104
124
  }
@@ -113,7 +133,7 @@ export function reduceDependencies({ node, data, key, pointer, path }: JsonSchem
113
133
 
114
134
  required = workingNode.schema.required ? workingNode.schema.required.concat(...required) : required;
115
135
  required = required.filter((r: string, index: number, list: string[]) => list.indexOf(r) === index);
116
- workingNode = mergeNode(workingNode, workingNode, "dependencies") as SchemaNode;
136
+ workingNode = mergeNode(workingNode, workingNode, KEYWORD) as SchemaNode;
117
137
  return workingNode.compileSchema(
118
138
  { ...workingNode.schema, required },
119
139
  workingNode.evaluationPath,