json-schema-library 11.1.0 → 11.3.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 +11 -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-jlib.ts +150 -0
- package/bowtie/bowtie.test.ts +267 -0
- package/bowtie/package.json +11 -0
- package/bowtie/tsconfig.json +12 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +66 -503
- package/dist/index.d.mts +66 -503
- 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.test.ts +52 -0
- package/src/compileSchema.ts +53 -3
- 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/draft04.ts +2 -2
- package/src/draft06/keywords/$ref.ts +15 -5
- package/src/draft06.ts +2 -2
- package/src/draft07.ts +2 -2
- package/src/draft2019-09/keywords/$ref.test.ts +3 -1
- package/src/draft2019-09/keywords/$ref.ts +44 -30
- 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/draft2019.ts +1 -1
- package/src/draft2020.ts +1 -1
- 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 +50 -17
- package/src/keywords/oneOf.test.ts +3 -3
- package/src/keywords/properties.ts +1 -1
- package/src/keywords/propertyDependencies.ts +1 -1
- package/src/methods/getData.ts +1 -1
- package/src/settings.ts +27 -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
|
@@ -2,6 +2,9 @@ import { compileSchema } from "./compileSchema";
|
|
|
2
2
|
import { strict as assert } from "assert";
|
|
3
3
|
import { draftEditor } from "./draftEditor";
|
|
4
4
|
import { SchemaNode } from "./SchemaNode";
|
|
5
|
+
import { draft04 } from "./draft04";
|
|
6
|
+
import { draft07 } from "./draft07";
|
|
7
|
+
import { draft2020 } from "./draft2020";
|
|
5
8
|
|
|
6
9
|
describe("compileSchema vocabulary", () => {
|
|
7
10
|
let root: SchemaNode;
|
|
@@ -33,6 +36,54 @@ describe("compileSchema vocabulary", () => {
|
|
|
33
36
|
});
|
|
34
37
|
});
|
|
35
38
|
|
|
39
|
+
describe("compileSchema draft-version", () => {
|
|
40
|
+
it("should select last draft", () => {
|
|
41
|
+
const node = compileSchema({}, { drafts: [draft04, draft07, draft2020] });
|
|
42
|
+
assert(node.getDraftVersion(), "draft-2020");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should select draft specified by $schema", () => {
|
|
46
|
+
const node = compileSchema(
|
|
47
|
+
{ $schema: "http://json-schema.org/draft-07/schema#" },
|
|
48
|
+
{ drafts: [draft04, draft07, draft2020] }
|
|
49
|
+
);
|
|
50
|
+
assert(node.getDraftVersion(), "draft-07");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should select fallback draft specified by `draft`", () => {
|
|
54
|
+
const node = compileSchema(
|
|
55
|
+
{},
|
|
56
|
+
{
|
|
57
|
+
draft: "http://json-schema.org/draft-07/schema#",
|
|
58
|
+
drafts: [draft04, draft07, draft2020]
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
assert(node.getDraftVersion(), "draft-07");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should prefer `$schema` over `draft` options", () => {
|
|
65
|
+
const node = compileSchema(
|
|
66
|
+
{ $schema: "http://json-schema.org/draft-04/schema#" },
|
|
67
|
+
{
|
|
68
|
+
draft: "http://json-schema.org/draft-07/schema#",
|
|
69
|
+
drafts: [draft04, draft07, draft2020]
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
assert(node.getDraftVersion(), "draft-04");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should always fallback to last draft if nothing matches", () => {
|
|
76
|
+
const node = compileSchema(
|
|
77
|
+
{ $schema: "http://json-schema.org/draft-A/schema#" },
|
|
78
|
+
{
|
|
79
|
+
draft: "http://json-schema.org/draft-B/schema#",
|
|
80
|
+
drafts: [draft04, draft07, draft2020]
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
assert(node.getDraftVersion(), "draft-2020");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
36
87
|
describe("compileSchema vocabulary", () => {
|
|
37
88
|
it("should add remote schema on compile", () => {
|
|
38
89
|
const remote = compileSchema({
|
|
@@ -150,6 +201,7 @@ describe("compileSchema `schemaLocation`", () => {
|
|
|
150
201
|
$defs: { asset: { type: "string" } }
|
|
151
202
|
});
|
|
152
203
|
|
|
204
|
+
assert(node.if && node.then && node.properties && node.$defs);
|
|
153
205
|
assert.deepEqual(node.schemaLocation, "#");
|
|
154
206
|
assert.deepEqual(node.if.schemaLocation, "#/if");
|
|
155
207
|
assert.deepEqual(node.then.schemaLocation, "#/then");
|
package/src/compileSchema.ts
CHANGED
|
@@ -25,14 +25,63 @@ import sanitizeErrors from "./utils/sanitizeErrors";
|
|
|
25
25
|
const { REGEX_FLAGS } = settings;
|
|
26
26
|
|
|
27
27
|
export type CompileOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* List of drafts to support.
|
|
30
|
+
*
|
|
31
|
+
* Drafts are selected by testing the passed `schema.$schema` for a matching id, which
|
|
32
|
+
* is tested by each draft's `Draft.$schemaRegEx`. In case no draft matches `schema.$schema`
|
|
33
|
+
* the last draft in the list will be used.
|
|
34
|
+
*
|
|
35
|
+
* @default [draft04, draft06, draft07, draft2019, draft2020]
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* import { draft04, draft07, draft2020 } from "json-schema-library"
|
|
39
|
+
* compileSchema({ $schema: "draft-04" }, { drafts: [draft04, draft07, draft2020] })
|
|
40
|
+
*/
|
|
28
41
|
drafts?: Draft[];
|
|
42
|
+
/**
|
|
43
|
+
* Fallback _draft_ version in case no _draft_ is specified by `schema.$schema`.
|
|
44
|
+
*
|
|
45
|
+
* Drafts are selected by given `schema.$schema` or the last draft from `drafts` as a fallback.
|
|
46
|
+
* Specifying `draft` will workthe same as a specifying `schema.$schema` in case no $schema is
|
|
47
|
+
* defined. When no match can be found, the last _draft_ from `drafts` will be used.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // uses draft-04
|
|
51
|
+
* compileSchema({ $schema: "draft-04" }, { drafts: [draft04, draft07, draft2020] })
|
|
52
|
+
*
|
|
53
|
+
* // uses draft-2020-12
|
|
54
|
+
* compileSchema({}, { drafts: [draft04, draft07, draft2020] })
|
|
55
|
+
*
|
|
56
|
+
* // uses draft-07
|
|
57
|
+
* compileSchema({}, { draft: "draft-07", drafts: [draft04, draft07, draft2020] })
|
|
58
|
+
|
|
59
|
+
* // uses draft-04
|
|
60
|
+
* compileSchema({ $schema: "draft-04" }, { draft: "draft-07", drafts: [draft04, draft07, draft2020] })
|
|
61
|
+
*
|
|
62
|
+
* // uses draft-2020
|
|
63
|
+
* compileSchema({ $schema: "draft-04" }, { draft: "draft-07", drafts: [draft2020] })
|
|
64
|
+
*/
|
|
65
|
+
draft?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Set node and its remote schemata as remote schemata for this node and schema to resolve $ref
|
|
68
|
+
*/
|
|
29
69
|
remote?: SchemaNode;
|
|
70
|
+
/**
|
|
71
|
+
* Enables `format`-keyword assertions when this is set tor `true` or sets assertion as defined by
|
|
72
|
+
* the given meta-schema. Set to `false` to deactivate format validation.
|
|
73
|
+
*
|
|
74
|
+
* @default true
|
|
75
|
+
*/
|
|
30
76
|
formatAssertion?: boolean | "meta-schema" | undefined;
|
|
77
|
+
/** Set default options for all `node.getData` requests */
|
|
31
78
|
getDataDefaultOptions?: TemplateOptions;
|
|
32
|
-
/**
|
|
79
|
+
/** Set to true to throw an Error on errors in input schema. Defaults to false */
|
|
33
80
|
throwOnInvalidSchema?: boolean;
|
|
34
|
-
/**
|
|
81
|
+
/** Set to true to collect unknown keywords of input schema in `node.schemaAnnotations`. Defaults to false */
|
|
35
82
|
withSchemaAnnotations?: boolean;
|
|
83
|
+
/** Set to true to throw an Error when encountering an unresolvable ref */
|
|
84
|
+
throwOnInvalidRef?: boolean;
|
|
36
85
|
};
|
|
37
86
|
|
|
38
87
|
const defaultDrafts: Draft[] = [draft04, draft06, draft07, draft2019, draft2020];
|
|
@@ -49,7 +98,7 @@ function getDraft(drafts: Draft[], $schema: string) {
|
|
|
49
98
|
export function compileSchema(schema: JsonSchema | BooleanSchema, options: CompileOptions = {}) {
|
|
50
99
|
let formatAssertion = options.formatAssertion ?? true;
|
|
51
100
|
const drafts = options.drafts ?? defaultDrafts;
|
|
52
|
-
const draft = getDraft(drafts, isJsonSchema(schema) ? schema.$schema : undefined);
|
|
101
|
+
const draft = getDraft(drafts, isJsonSchema(schema) ? (options.draft ?? schema.$schema) : undefined);
|
|
53
102
|
const node: SchemaNode & { schemaErrors?: JsonError[]; schemaAnnotations: JsonAnnotation[] } = {
|
|
54
103
|
evaluationPath: "#",
|
|
55
104
|
lastIdPointer: "#",
|
|
@@ -69,6 +118,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
|
|
|
69
118
|
...copy(pick(draft, "methods", "keywords", "version", "formats", "errors")),
|
|
70
119
|
getDataDefaultOptions: options.getDataDefaultOptions,
|
|
71
120
|
withSchemaAnnotations: options.withSchemaAnnotations ?? false,
|
|
121
|
+
throwOnInvalidRef: options.throwOnInvalidRef ?? false,
|
|
72
122
|
drafts
|
|
73
123
|
},
|
|
74
124
|
...SchemaNodeMethods
|
|
@@ -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
|
+
});
|
package/src/draft04.ts
CHANGED
|
@@ -61,8 +61,8 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
|
|
|
61
61
|
*/
|
|
62
62
|
export const draft04 = sanitizeKeywords({
|
|
63
63
|
version: "draft-04",
|
|
64
|
-
$schemaRegEx: "draft
|
|
65
|
-
$schema: "http://json-schema.org/draft-04/schema",
|
|
64
|
+
$schemaRegEx: "draft-?0?4",
|
|
65
|
+
$schema: "http://json-schema.org/draft-04/schema#",
|
|
66
66
|
errors,
|
|
67
67
|
formats,
|
|
68
68
|
methods: {
|
|
@@ -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);
|
package/src/draft06.ts
CHANGED
|
@@ -62,8 +62,8 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
|
|
|
62
62
|
*/
|
|
63
63
|
export const draft06 = sanitizeKeywords({
|
|
64
64
|
version: "draft-06",
|
|
65
|
-
$schemaRegEx: "draft
|
|
66
|
-
$schema: "http://json-schema.org/draft-06/schema",
|
|
65
|
+
$schemaRegEx: "draft-?0?6",
|
|
66
|
+
$schema: "http://json-schema.org/draft-06/schema#",
|
|
67
67
|
errors,
|
|
68
68
|
formats,
|
|
69
69
|
methods: {
|
package/src/draft07.ts
CHANGED
|
@@ -53,8 +53,8 @@ import { uniqueItemsKeyword } from "./keywords/uniqueItems";
|
|
|
53
53
|
*/
|
|
54
54
|
export const draft07 = sanitizeKeywords({
|
|
55
55
|
version: "draft-07",
|
|
56
|
-
$schemaRegEx: "draft
|
|
57
|
-
$schema: "http://json-schema.org/draft-07/schema",
|
|
56
|
+
$schemaRegEx: "draft-?0?7",
|
|
57
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
58
58
|
errors,
|
|
59
59
|
formats,
|
|
60
60
|
methods: {
|
|
@@ -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", () => {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Keyword, JsonSchemaValidatorParams, ValidationPath } from "../../Keyword";
|
|
2
2
|
import { resolveUri } from "../../utils/resolveUri";
|
|
3
3
|
import splitRef from "../../utils/splitRef";
|
|
4
|
-
import { omit } from "../../utils/omit";
|
|
5
|
-
import { isObject } from "../../utils/isObject";
|
|
6
4
|
import { validateNode } from "../../validateNode";
|
|
7
|
-
import { SchemaNode } from "../../types";
|
|
5
|
+
import { isSchemaNode, JsonError, SchemaNode } from "../../types";
|
|
8
6
|
import { get, split } from "@sagold/json-pointer";
|
|
9
|
-
import { reduceRef } from "../../keywords/$ref";
|
|
7
|
+
import { reduceRef, compileNext } from "../../keywords/$ref";
|
|
10
8
|
|
|
11
9
|
export const $refKeyword: Keyword = {
|
|
12
10
|
id: "$ref",
|
|
@@ -73,7 +71,9 @@ export function parseRef(node: SchemaNode) {
|
|
|
73
71
|
export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: string; path?: ValidationPath } = {}) {
|
|
74
72
|
if (this.schema.$recursiveRef) {
|
|
75
73
|
const nextNode = resolveRecursiveRef(this, path ?? []);
|
|
76
|
-
|
|
74
|
+
if (isSchemaNode(nextNode)) {
|
|
75
|
+
path?.push({ pointer: pointer!, node: nextNode! });
|
|
76
|
+
}
|
|
77
77
|
return nextNode;
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -82,10 +82,8 @@ export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: stri
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const resolvedNode = getRef(this);
|
|
85
|
-
if (resolvedNode
|
|
85
|
+
if (isSchemaNode(resolvedNode)) {
|
|
86
86
|
path?.push({ pointer: pointer!, node: resolvedNode });
|
|
87
|
-
} else {
|
|
88
|
-
// console.log("failed resolving", node.$ref, "from", Object.keys(node.context.refs));
|
|
89
87
|
}
|
|
90
88
|
return resolvedNode;
|
|
91
89
|
}
|
|
@@ -93,13 +91,18 @@ export function resolveRef(this: SchemaNode, { pointer, path }: { pointer?: stri
|
|
|
93
91
|
function validateRef({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
|
|
94
92
|
const nextNode = node.resolveRef({ pointer, path });
|
|
95
93
|
if (nextNode != null) {
|
|
96
|
-
// recursively resolveRef and validate
|
|
97
94
|
return validateNode(nextNode, data, pointer, path);
|
|
98
95
|
}
|
|
96
|
+
return node.createError("ref-error", {
|
|
97
|
+
ref: node.schema.$ref ?? node.schema.$recursiveRef,
|
|
98
|
+
pointer,
|
|
99
|
+
schema: node.schema,
|
|
100
|
+
value: data
|
|
101
|
+
});
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// 1. https://json-schema.org/draft/2019-09/json-schema-core#scopes
|
|
102
|
-
function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode |
|
|
105
|
+
function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode | JsonError {
|
|
103
106
|
const history = path;
|
|
104
107
|
|
|
105
108
|
// RESTRICT BY CHANGE IN BASE-URL
|
|
@@ -127,33 +130,30 @@ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode
|
|
|
127
130
|
return nextNode;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
function
|
|
131
|
-
const referencedSchema = isObject(referencedNode.schema)
|
|
132
|
-
? omit(referencedNode.schema, "$id")
|
|
133
|
-
: referencedNode.schema;
|
|
134
|
-
|
|
135
|
-
return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedNode.schemaLocation);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
|
|
133
|
+
export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | JsonError {
|
|
139
134
|
if ($ref == null) {
|
|
140
135
|
return node;
|
|
141
136
|
}
|
|
142
137
|
|
|
143
138
|
// resolve $ref by json-evaluationPath
|
|
144
139
|
if (node.context.refs[$ref]) {
|
|
145
|
-
return compileNext(node.context.refs[$ref], node
|
|
140
|
+
return compileNext(node.context.refs[$ref], node);
|
|
146
141
|
}
|
|
147
142
|
// resolve $ref from $anchor
|
|
148
143
|
if (node.context.anchors[$ref]) {
|
|
149
|
-
return compileNext(node.context.anchors[$ref], node
|
|
144
|
+
return compileNext(node.context.anchors[$ref], node);
|
|
150
145
|
}
|
|
151
146
|
|
|
152
147
|
// check for remote-host + pointer pair to switch rootSchema
|
|
153
148
|
const fragments = splitRef($ref);
|
|
154
149
|
if (fragments.length === 0) {
|
|
155
150
|
// console.error("REF: INVALID", $ref);
|
|
156
|
-
return
|
|
151
|
+
return node.createError("ref-error", {
|
|
152
|
+
ref: $ref,
|
|
153
|
+
pointer: node.evaluationPath,
|
|
154
|
+
schema: node.schema,
|
|
155
|
+
value: undefined
|
|
156
|
+
});
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// resolve $ref as remote-host
|
|
@@ -161,7 +161,7 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
|
|
|
161
161
|
const $ref = fragments[0];
|
|
162
162
|
// this is a reference to remote-host root node
|
|
163
163
|
if (node.context.remotes[$ref]) {
|
|
164
|
-
return compileNext(node.context.remotes[$ref], node
|
|
164
|
+
return compileNext(node.context.remotes[$ref], node);
|
|
165
165
|
}
|
|
166
166
|
if ($ref[0] === "#") {
|
|
167
167
|
// @todo there is a bug joining multiple fragments to e.g. #/base#/examples/0
|
|
@@ -175,7 +175,12 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
// console.error("REF: UNFOUND 1", $ref);
|
|
178
|
-
return
|
|
178
|
+
return node.createError("ref-error", {
|
|
179
|
+
ref: $ref,
|
|
180
|
+
pointer: node.evaluationPath,
|
|
181
|
+
schema: node.schema,
|
|
182
|
+
value: undefined
|
|
183
|
+
});
|
|
179
184
|
}
|
|
180
185
|
|
|
181
186
|
if (fragments.length === 2) {
|
|
@@ -206,16 +211,25 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
|
|
|
206
211
|
// @ts-expect-error random path
|
|
207
212
|
currentNode = currentNode[property];
|
|
208
213
|
if (currentNode == null) {
|
|
209
|
-
console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
|
|
210
|
-
return
|
|
214
|
+
// console.error("REF: FAILED RESOLVING ref json-pointer", fragments[1]);
|
|
215
|
+
return node.createError("ref-error", {
|
|
216
|
+
ref: $ref,
|
|
217
|
+
pointer: node.evaluationPath,
|
|
218
|
+
schema: node.schema,
|
|
219
|
+
value: undefined,
|
|
220
|
+
host: fragments[0],
|
|
221
|
+
local: fragments[1]
|
|
222
|
+
});
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
225
|
return currentNode;
|
|
214
226
|
}
|
|
215
|
-
|
|
216
|
-
// console.error("REF: UNFOUND 2", $ref);
|
|
217
|
-
return undefined;
|
|
218
227
|
}
|
|
219
228
|
|
|
220
|
-
|
|
229
|
+
return node.createError("ref-error", {
|
|
230
|
+
ref: $ref,
|
|
231
|
+
pointer: node.evaluationPath,
|
|
232
|
+
schema: node.schema,
|
|
233
|
+
value: undefined
|
|
234
|
+
});
|
|
221
235
|
}
|
|
@@ -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) {
|