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.
- package/.mocharc.js +1 -0
- package/CHANGELOG.md +6 -0
- package/README.md +21 -13
- package/bowtie/.editorconfig +21 -0
- package/bowtie/.prettierrc +6 -0
- package/bowtie/BOWTIE.md +54 -0
- package/bowtie/Dockerfile +6 -0
- package/bowtie/bowtie-api.ts +101 -0
- package/bowtie/bowtie.test.ts +76 -0
- package/bowtie/bowtie.ts +156 -0
- package/bowtie/package.json +11 -0
- package/bowtie/tsconfig.json +12 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -514
- package/dist/index.d.mts +6 -514
- package/dist/index.mjs +1 -1
- package/dist/jlib.js +2 -13
- package/dist/remotes/index.cjs +1 -0
- package/dist/remotes/index.d.cts +7 -0
- package/dist/remotes/index.d.mts +7 -0
- package/dist/remotes/index.mjs +1 -0
- package/dist/types-B2wwNWyo.d.cts +513 -0
- package/dist/types-BhTU1l2h.d.mts +513 -0
- package/index.ts +0 -3
- package/package.json +14 -8
- package/src/Draft.ts +1 -1
- package/src/Keyword.ts +2 -3
- package/src/SchemaNode.ts +9 -0
- package/src/compileSchema.ts +4 -1
- package/src/draft04/keywords/$ref.ts +22 -14
- package/src/draft04/keywords/exclusiveMaximum.ts +14 -0
- package/src/draft04/keywords/exclusiveMinimum.ts +14 -0
- package/src/draft04/validateSchema.test.ts +20 -0
- package/src/draft06/keywords/$ref.ts +15 -5
- package/src/draft2019-09/keywords/$ref.test.ts +3 -1
- package/src/draft2019-09/keywords/$ref.ts +40 -16
- package/src/draft2019-09/keywords/additionalItems.ts +33 -10
- package/src/draft2019-09/keywords/items.ts +32 -10
- package/src/draft2019-09/keywords/unevaluatedItems.ts +19 -6
- package/src/draft2019-09/methods/getData.ts +1 -1
- package/src/draft2019-09/validateSchema.test.ts +28 -0
- package/src/errors/errors.ts +4 -0
- package/src/formats/formats.ts +35 -28
- package/src/formats/hyperjump.d.ts +172 -0
- package/src/keywords/$ref.ts +47 -13
- package/src/keywords/properties.ts +1 -1
- package/src/keywords/propertyDependencies.ts +1 -1
- package/src/methods/getData.ts +1 -1
- package/src/validateNode.ts +4 -1
- package/tsconfig.json +11 -4
- package/tsconfig.test.json +9 -2
- package/tsdown.config.ts +1 -1
- package/Dockerfile +0 -6
- package/bowtie_jlib.js +0 -140
- package/remotes/draft04.json +0 -150
- package/remotes/draft06.json +0 -155
- package/remotes/draft07.json +0 -155
- package/remotes/draft2019-09.json +0 -42
- package/remotes/draft2019-09_meta_applicator.json +0 -53
- package/remotes/draft2019-09_meta_content.json +0 -14
- package/remotes/draft2019-09_meta_core.json +0 -54
- package/remotes/draft2019-09_meta_format.json +0 -11
- package/remotes/draft2019-09_meta_meta-data.json +0 -34
- package/remotes/draft2019-09_meta_validation.json +0 -95
- package/remotes/draft2020-12.json +0 -55
- package/remotes/draft2020-12_meta_applicator.json +0 -45
- package/remotes/draft2020-12_meta_content.json +0 -14
- package/remotes/draft2020-12_meta_core.json +0 -48
- package/remotes/draft2020-12_meta_format_annotation.json +0 -11
- package/remotes/draft2020-12_meta_format_assertion.json +0 -11
- package/remotes/draft2020-12_meta_meta_data.json +0 -34
- package/remotes/draft2020-12_meta_unevaluated.json +0 -12
- package/remotes/draft2020-12_meta_validation.json +0 -87
- 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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
//
|
|
148
|
+
// @todo returning error here breaks specs
|
|
146
149
|
return undefined;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
|
|
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
|
|
55
|
-
return
|
|
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
|
|
63
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
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 |
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
9
|
-
keyword:
|
|
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 (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
8
|
-
keyword:
|
|
9
|
+
id: KEYWORD,
|
|
10
|
+
keyword: KEYWORD,
|
|
9
11
|
parse: parseItems,
|
|
10
|
-
addResolve: (node) => (node.prefixItems || node
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
`${evaluationPath}
|
|
31
|
-
`${node.schemaLocation}
|
|
36
|
+
items,
|
|
37
|
+
`${evaluationPath}/${KEYWORD}`,
|
|
38
|
+
`${node.schemaLocation}/${KEYWORD}`
|
|
32
39
|
);
|
|
33
40
|
node.items = propertyNode;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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:
|
|
13
|
-
keyword:
|
|
14
|
+
id: KEYWORD,
|
|
15
|
+
keyword: KEYWORD,
|
|
14
16
|
parse: parseUnevaluatedItems,
|
|
15
|
-
addValidate: ({ schema }) => schema
|
|
17
|
+
addValidate: ({ schema }) => schema[KEYWORD] != null,
|
|
16
18
|
validate: validateUnevaluatedItems
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export function parseUnevaluatedItems(node: SchemaNode) {
|
|
20
|
-
|
|
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}
|
|
26
|
-
`${node.schemaLocation}
|
|
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
|
|
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
|
+
});
|
package/src/errors/errors.ts
CHANGED
|
@@ -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}}'",
|