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.
- package/.mocharc.js +1 -0
- package/CHANGELOG.md +17 -0
- package/README.md +120 -0
- 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 +39 -470
- package/dist/index.d.mts +39 -470
- 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 +10 -4
- package/package.json +14 -8
- package/src/Keyword.ts +37 -12
- package/src/SchemaNode.ts +84 -16
- package/src/compileSchema.ts +56 -4
- 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 +8 -1
- package/src/formats/formats.ts +35 -28
- package/src/formats/hyperjump.d.ts +172 -0
- package/src/keywords/$defs.ts +34 -8
- package/src/keywords/$ref.ts +59 -13
- package/src/keywords/additionalProperties.ts +19 -8
- package/src/keywords/allOf.ts +44 -18
- package/src/keywords/anyOf.ts +38 -19
- package/src/keywords/contains.ts +21 -9
- package/src/keywords/dependencies.ts +37 -17
- package/src/keywords/dependentRequired.ts +56 -38
- package/src/keywords/dependentSchemas.ts +37 -13
- package/src/keywords/deprecated.ts +32 -8
- package/src/keywords/enum.ts +30 -8
- package/src/keywords/exclusiveMaximum.ts +21 -2
- package/src/keywords/exclusiveMinimum.ts +22 -3
- package/src/keywords/format.ts +21 -2
- package/src/keywords/ifthenelse.ts +49 -5
- package/src/keywords/items.ts +27 -13
- package/src/keywords/maxItems.ts +22 -2
- package/src/keywords/maxLength.ts +30 -9
- package/src/keywords/maxProperties.ts +30 -9
- package/src/keywords/maximum.ts +28 -8
- package/src/keywords/minItems.ts +30 -9
- package/src/keywords/minLength.ts +30 -9
- package/src/keywords/minProperties.ts +26 -5
- package/src/keywords/minimum.ts +32 -13
- package/src/keywords/multipleOf.ts +33 -12
- package/src/keywords/not.ts +23 -10
- package/src/keywords/oneOf.ts +29 -9
- package/src/keywords/pattern.ts +35 -9
- package/src/keywords/properties.ts +34 -11
- package/src/keywords/propertyDependencies.test.ts +180 -0
- package/src/keywords/propertyDependencies.ts +173 -0
- package/src/keywords/propertyNames.ts +26 -14
- package/src/keywords/required.ts +31 -8
- package/src/keywords/type.ts +53 -16
- package/src/keywords/unevaluatedItems.ts +24 -8
- package/src/keywords/unevaluatedProperties.ts +24 -7
- package/src/keywords/uniqueItems.ts +23 -4
- package/src/mergeNode.ts +9 -4
- package/src/methods/getData.ts +1 -1
- package/src/settings.ts +2 -1
- package/src/types.ts +1 -1
- package/src/utils/isListOfStrings.ts +3 -0
- package/src/validate.test.ts +0 -2
- package/src/validateNode.ts +6 -3
- package/src/validateSchema.test.ts +312 -0
- 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
package/src/SchemaNode.ts
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
OptionalNodeOrError,
|
|
25
25
|
NodeOrError,
|
|
26
26
|
JsonAnnotation,
|
|
27
|
-
isJsonAnnotation
|
|
27
|
+
isJsonAnnotation,
|
|
28
|
+
isBooleanSchema
|
|
28
29
|
} from "./types";
|
|
29
30
|
import { isObject } from "./utils/isObject";
|
|
30
31
|
import { join } from "@sagold/json-pointer";
|
|
@@ -91,6 +92,10 @@ export type Context = {
|
|
|
91
92
|
formats: Draft["formats"];
|
|
92
93
|
/** [SHARED USING ADD REMOTE] getData default options */
|
|
93
94
|
getDataDefaultOptions?: TemplateOptions;
|
|
95
|
+
/** [SHARED USING ADD REMOTE] collect unknown keywords in schemaAnnotations */
|
|
96
|
+
withSchemaAnnotations?: boolean;
|
|
97
|
+
/** [SHARED USING ADD REMOTE] throw error on validation when ref cannot be resolved */
|
|
98
|
+
throwOnInvalidRef?: boolean;
|
|
94
99
|
};
|
|
95
100
|
|
|
96
101
|
export interface SchemaNode extends SchemaNodeMethodsType {
|
|
@@ -129,6 +134,7 @@ export interface SchemaNode extends SchemaNodeMethodsType {
|
|
|
129
134
|
reducers: JsonSchemaReducer[];
|
|
130
135
|
resolvers: JsonSchemaResolver[];
|
|
131
136
|
validators: JsonSchemaValidator[];
|
|
137
|
+
schemaValidation?: ValidationAnnotation[];
|
|
132
138
|
|
|
133
139
|
// parsed schema properties (registered by parsers)
|
|
134
140
|
$id?: string;
|
|
@@ -140,7 +146,9 @@ export interface SchemaNode extends SchemaNodeMethodsType {
|
|
|
140
146
|
contains?: SchemaNode;
|
|
141
147
|
dependentRequired?: Record<string, string[]>;
|
|
142
148
|
dependentSchemas?: Record<string, SchemaNode | boolean>;
|
|
149
|
+
deprecated?: boolean;
|
|
143
150
|
else?: SchemaNode;
|
|
151
|
+
enum?: unknown[];
|
|
144
152
|
if?: SchemaNode;
|
|
145
153
|
/**
|
|
146
154
|
* # Items-array schema - for all drafts
|
|
@@ -177,21 +185,40 @@ export interface SchemaNode extends SchemaNodeMethodsType {
|
|
|
177
185
|
* | [AdditionalItems Specification](https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02#additionalItems)
|
|
178
186
|
*/
|
|
179
187
|
items?: SchemaNode;
|
|
188
|
+
maximum?: number;
|
|
189
|
+
minimum?: number;
|
|
190
|
+
maxItems?: number;
|
|
191
|
+
maxLength?: number;
|
|
192
|
+
maxProperties?: number;
|
|
193
|
+
minItems?: number;
|
|
194
|
+
minLength?: number;
|
|
195
|
+
minProperties?: number;
|
|
180
196
|
not?: SchemaNode;
|
|
181
197
|
oneOf?: SchemaNode[];
|
|
198
|
+
multipleOf?: number;
|
|
199
|
+
pattern?: RegExp;
|
|
182
200
|
patternProperties?: { name: string; pattern: RegExp; node: SchemaNode }[];
|
|
201
|
+
propertyDependencies?: Record<string, Record<string, SchemaNode>>;
|
|
183
202
|
properties?: Record<string, SchemaNode>;
|
|
184
203
|
propertyNames?: SchemaNode;
|
|
204
|
+
required?: string[];
|
|
185
205
|
then?: SchemaNode;
|
|
206
|
+
type?: string | string[];
|
|
186
207
|
unevaluatedItems?: SchemaNode;
|
|
187
208
|
unevaluatedProperties?: SchemaNode;
|
|
209
|
+
uniqueItems?: true;
|
|
188
210
|
}
|
|
189
211
|
|
|
190
212
|
/**
|
|
191
213
|
* Fixed SchemaNode mixin methods
|
|
192
214
|
*/
|
|
193
215
|
interface SchemaNodeMethodsType {
|
|
194
|
-
compileSchema(
|
|
216
|
+
compileSchema(
|
|
217
|
+
schema: JsonSchema | BooleanSchema,
|
|
218
|
+
evaluationPath?: string,
|
|
219
|
+
schemaLocation?: string,
|
|
220
|
+
dynamicId?: string
|
|
221
|
+
): SchemaNode;
|
|
195
222
|
createError<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonError;
|
|
196
223
|
createAnnotation<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonAnnotation;
|
|
197
224
|
createSchema(data?: unknown): JsonSchema;
|
|
@@ -325,7 +352,20 @@ export const SchemaNodeMethods = {
|
|
|
325
352
|
...SchemaNodeMethods
|
|
326
353
|
};
|
|
327
354
|
|
|
328
|
-
|
|
355
|
+
if (!isJsonSchema(schema) && !isBooleanSchema(schema)) {
|
|
356
|
+
node.schemaValidation = [
|
|
357
|
+
node.createError("schema-error", {
|
|
358
|
+
pointer: schemaLocation ?? evaluationPath,
|
|
359
|
+
schema,
|
|
360
|
+
value: undefined,
|
|
361
|
+
message: `JSON schema must be object or boolean - reveived: '${schema}'`
|
|
362
|
+
})
|
|
363
|
+
];
|
|
364
|
+
return node;
|
|
365
|
+
}
|
|
366
|
+
const schemaValidation = addKeywords(node).filter((err) => err != null);
|
|
367
|
+
node.schemaValidation = sanitizeErrors(schemaValidation);
|
|
368
|
+
|
|
329
369
|
return node;
|
|
330
370
|
},
|
|
331
371
|
|
|
@@ -474,6 +514,13 @@ export const SchemaNodeMethods = {
|
|
|
474
514
|
const errorsAsync: Promise<Maybe<ValidationAnnotation>[]>[] = [];
|
|
475
515
|
sanitizeErrors(Array.isArray(errors) ? errors : [errors]).forEach((error) => {
|
|
476
516
|
if (isJsonError(error)) {
|
|
517
|
+
if (node.context.throwOnInvalidRef && error.code === "ref-error") {
|
|
518
|
+
const refError = new Error("Invalid $ref: " + error.message);
|
|
519
|
+
// @ts-expect-error unknown error-property
|
|
520
|
+
refError.data = syncErrors;
|
|
521
|
+
throw refError;
|
|
522
|
+
}
|
|
523
|
+
|
|
477
524
|
syncErrors.push(error);
|
|
478
525
|
} else if (error instanceof Promise) {
|
|
479
526
|
errorsAsync.push(error.then(sanitizeErrors));
|
|
@@ -497,15 +544,15 @@ export const SchemaNodeMethods = {
|
|
|
497
544
|
* @returns the current node (not the remote schema-node)
|
|
498
545
|
*/
|
|
499
546
|
addRemoteSchema(url: string, schema: JsonSchema | BooleanSchema): SchemaNode {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
547
|
+
// @draft >= 6
|
|
548
|
+
if (isJsonSchema(schema)) {
|
|
549
|
+
schema.$id = resolveUri(schema.$id || url);
|
|
550
|
+
}
|
|
551
|
+
|
|
505
552
|
const node = this as SchemaNode;
|
|
506
553
|
const { context } = node;
|
|
507
554
|
const schemaId = isJsonSchema(schema) ? schema.$schema : undefined;
|
|
508
|
-
const draft = getDraft(context.drafts, schemaId ?? context.rootNode.schema?.$schema);
|
|
555
|
+
const draft = getDraft(context.drafts, schemaId ?? context.rootNode.schema?.$schema);
|
|
509
556
|
|
|
510
557
|
const remoteNode: SchemaNode = {
|
|
511
558
|
evaluationPath: "#",
|
|
@@ -565,20 +612,40 @@ const noRefMergeDrafts = ["draft-04", "draft-06", "draft-07"];
|
|
|
565
612
|
export function addKeywords(node: SchemaNode) {
|
|
566
613
|
if (node.schema.$ref && noRefMergeDrafts.includes(node.context.version)) {
|
|
567
614
|
// for these draft versions only ref is validated
|
|
568
|
-
node.context.keywords
|
|
615
|
+
return node.context.keywords
|
|
569
616
|
.filter(({ keyword }) => whitelist.includes(keyword))
|
|
570
|
-
.
|
|
571
|
-
return;
|
|
617
|
+
.map((keyword) => execKeyword(keyword, node));
|
|
572
618
|
}
|
|
573
619
|
const keys = Object.keys(node.schema);
|
|
574
|
-
node.context.keywords
|
|
575
|
-
.filter(({ keyword }) =>
|
|
576
|
-
.
|
|
620
|
+
const errors = node.context.keywords
|
|
621
|
+
.filter(({ keyword }) => whitelist.includes(keyword) || keys.includes(keyword))
|
|
622
|
+
.map((keyword) => execKeyword(keyword, node));
|
|
623
|
+
|
|
624
|
+
// find unused keywords
|
|
625
|
+
if (node.context.withSchemaAnnotations) {
|
|
626
|
+
Object.keys(node.schema)
|
|
627
|
+
.filter(
|
|
628
|
+
(key) =>
|
|
629
|
+
!key.startsWith("x-") && node.context.keywords.find((keyword) => keyword.keyword === key) == null
|
|
630
|
+
)
|
|
631
|
+
.forEach((keyword) => {
|
|
632
|
+
errors.push(
|
|
633
|
+
node.createAnnotation("unknown-keyword-warning", {
|
|
634
|
+
pointer: `${node.schemaLocation}/${keyword}`,
|
|
635
|
+
schema: node.schema,
|
|
636
|
+
value: keyword,
|
|
637
|
+
draft: node.getDraftVersion()
|
|
638
|
+
})
|
|
639
|
+
);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return errors;
|
|
577
644
|
}
|
|
578
645
|
|
|
579
646
|
export function execKeyword(keyword: Keyword, node: SchemaNode) {
|
|
580
647
|
// @todo consider first parsing all nodes
|
|
581
|
-
keyword.parse?.(node);
|
|
648
|
+
const errors = keyword.parse?.(node);
|
|
582
649
|
if (keyword.reduce && keyword.addReduce?.(node)) {
|
|
583
650
|
node.reducers.push(keyword.reduce);
|
|
584
651
|
}
|
|
@@ -588,4 +655,5 @@ export function execKeyword(keyword: Keyword, node: SchemaNode) {
|
|
|
588
655
|
if (keyword.validate && keyword.addValidate?.(node)) {
|
|
589
656
|
node.validators.push(keyword.validate);
|
|
590
657
|
}
|
|
658
|
+
return errors;
|
|
591
659
|
}
|
package/src/compileSchema.ts
CHANGED
|
@@ -6,10 +6,21 @@ import { draft07 } from "./draft07";
|
|
|
6
6
|
import { draft2019 } from "./draft2019";
|
|
7
7
|
import { draft2020 } from "./draft2020";
|
|
8
8
|
import { pick } from "./utils/pick";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
JsonSchema,
|
|
11
|
+
BooleanSchema,
|
|
12
|
+
Draft,
|
|
13
|
+
isJsonSchema,
|
|
14
|
+
JsonAnnotation,
|
|
15
|
+
JsonError,
|
|
16
|
+
isJsonError,
|
|
17
|
+
isJsonAnnotation,
|
|
18
|
+
isBooleanSchema
|
|
19
|
+
} from "./types";
|
|
10
20
|
import { TemplateOptions } from "./methods/getData";
|
|
11
21
|
import { SchemaNode, SchemaNodeMethods, addKeywords, isSchemaNode } from "./SchemaNode";
|
|
12
22
|
import settings from "./settings";
|
|
23
|
+
import sanitizeErrors from "./utils/sanitizeErrors";
|
|
13
24
|
|
|
14
25
|
const { REGEX_FLAGS } = settings;
|
|
15
26
|
|
|
@@ -18,6 +29,12 @@ export type CompileOptions = {
|
|
|
18
29
|
remote?: SchemaNode;
|
|
19
30
|
formatAssertion?: boolean | "meta-schema" | undefined;
|
|
20
31
|
getDataDefaultOptions?: TemplateOptions;
|
|
32
|
+
/** set to true to throw an Error on errors in input schema. Defaults to false */
|
|
33
|
+
throwOnInvalidSchema?: boolean;
|
|
34
|
+
/** set to true to collect unknown keywords of input schema in `node.schemaAnnotations`. Defaults to false */
|
|
35
|
+
withSchemaAnnotations?: boolean;
|
|
36
|
+
/** set to true to throw an Error when encountering an unresolvable ref */
|
|
37
|
+
throwOnInvalidRef?: boolean;
|
|
21
38
|
};
|
|
22
39
|
|
|
23
40
|
const defaultDrafts: Draft[] = [draft04, draft06, draft07, draft2019, draft2020];
|
|
@@ -35,8 +52,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
|
|
|
35
52
|
let formatAssertion = options.formatAssertion ?? true;
|
|
36
53
|
const drafts = options.drafts ?? defaultDrafts;
|
|
37
54
|
const draft = getDraft(drafts, isJsonSchema(schema) ? schema.$schema : undefined);
|
|
38
|
-
|
|
39
|
-
const node: SchemaNode = {
|
|
55
|
+
const node: SchemaNode & { schemaErrors?: JsonError[]; schemaAnnotations: JsonAnnotation[] } = {
|
|
40
56
|
evaluationPath: "#",
|
|
41
57
|
lastIdPointer: "#",
|
|
42
58
|
schemaLocation: "#",
|
|
@@ -54,6 +70,8 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
|
|
|
54
70
|
refs: {},
|
|
55
71
|
...copy(pick(draft, "methods", "keywords", "version", "formats", "errors")),
|
|
56
72
|
getDataDefaultOptions: options.getDataDefaultOptions,
|
|
73
|
+
withSchemaAnnotations: options.withSchemaAnnotations ?? false,
|
|
74
|
+
throwOnInvalidRef: options.throwOnInvalidRef ?? false,
|
|
57
75
|
drafts
|
|
58
76
|
},
|
|
59
77
|
...SchemaNodeMethods
|
|
@@ -82,6 +100,40 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
|
|
|
82
100
|
node.context.keywords = node.context.keywords.filter((f) => f.keyword !== "format");
|
|
83
101
|
}
|
|
84
102
|
|
|
85
|
-
|
|
103
|
+
if (!isJsonSchema(schema) && !isBooleanSchema(schema)) {
|
|
104
|
+
node.schemaErrors = [
|
|
105
|
+
node.createError("schema-error", {
|
|
106
|
+
pointer: "#",
|
|
107
|
+
schema,
|
|
108
|
+
value: undefined,
|
|
109
|
+
message: `JSON schema must be object or boolean - reveived: '${schema}'`
|
|
110
|
+
})
|
|
111
|
+
];
|
|
112
|
+
return node;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// parse and validate schema
|
|
116
|
+
let schemaValidation = addKeywords(node).filter((err) => err != null);
|
|
117
|
+
schemaValidation = sanitizeErrors(schemaValidation);
|
|
118
|
+
const schemaErrors: JsonError[] = [];
|
|
119
|
+
const schemaAnnotations: JsonAnnotation[] = [];
|
|
120
|
+
schemaValidation.forEach((error) => {
|
|
121
|
+
if (isJsonError(error)) {
|
|
122
|
+
schemaErrors.push(error);
|
|
123
|
+
} else if (isJsonAnnotation(error)) {
|
|
124
|
+
schemaAnnotations.push(error);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (options.throwOnInvalidSchema && schemaErrors.length > 0) {
|
|
129
|
+
const error = new Error("Invalid schema passed to compileSchema");
|
|
130
|
+
// @ts-expect-error unknown error-property
|
|
131
|
+
error.data = schemaErrors;
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
node.schemaErrors = schemaErrors;
|
|
136
|
+
node.schemaAnnotations = schemaAnnotations;
|
|
137
|
+
|
|
86
138
|
return node;
|
|
87
139
|
}
|
|
@@ -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
|
}
|