z-schema 12.2.0 → 12.3.1
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/README.md +2 -2
- package/bin/z-schema +1 -1
- package/cjs/{index.js → index.cjs} +696 -687
- package/cjs/{index.d.ts → index.d.cts} +47 -26
- package/dist/{errors.d.mts → errors.d.ts} +2 -2
- package/dist/{errors.mjs → errors.js} +1 -2
- package/dist/{format-validators.mjs → format-validators.js} +43 -36
- package/dist/{index.d.mts → index.d.ts} +9 -9
- package/dist/{index.mjs → index.js} +3 -3
- package/dist/{json-schema-versions.d.mts → json-schema-versions.d.ts} +34 -3
- package/dist/{json-schema.d.mts → json-schema.d.ts} +7 -7
- package/dist/{json-schema.mjs → json-schema.js} +7 -12
- package/dist/{json-validation.mjs → json-validation.js} +143 -127
- package/dist/{report.d.mts → report.d.ts} +7 -8
- package/dist/{report.mjs → report.js} +28 -31
- package/dist/{schema-cache.d.mts → schema-cache.d.ts} +4 -4
- package/dist/{schema-cache.mjs → schema-cache.js} +10 -11
- package/dist/{schema-compiler.d.mts → schema-compiler.d.ts} +4 -4
- package/dist/{schema-compiler.mjs → schema-compiler.js} +95 -77
- package/dist/{schema-validator.d.mts → schema-validator.d.ts} +5 -5
- package/dist/{schema-validator.mjs → schema-validator.js} +138 -166
- package/dist/utils/{array.mjs → array.js} +4 -3
- package/dist/utils/{base64.mjs → base64.js} +3 -2
- package/dist/utils/{clone.mjs → clone.js} +18 -20
- package/dist/utils/{hostname.mjs → hostname.js} +19 -22
- package/dist/utils/{json.mjs → json.js} +11 -7
- package/dist/utils/{schema-regex.mjs → schema-regex.js} +5 -5
- package/dist/utils/{time.mjs → time.js} +5 -5
- package/dist/utils/unicode.js +22 -0
- package/dist/utils/{what-is.mjs → what-is.js} +1 -2
- package/dist/validation/{array.mjs → array.js} +18 -20
- package/dist/validation/{combinators.mjs → combinators.js} +16 -16
- package/dist/validation/{numeric.mjs → numeric.js} +11 -11
- package/dist/validation/{object.mjs → object.js} +35 -34
- package/dist/validation/{ref.mjs → ref.js} +4 -4
- package/dist/validation/{shared.mjs → shared.js} +12 -11
- package/dist/validation/{string.mjs → string.js} +32 -32
- package/dist/validation/type.js +34 -0
- package/dist/{z-schema-base.d.mts → z-schema-base.d.ts} +11 -12
- package/dist/{z-schema-base.mjs → z-schema-base.js} +45 -40
- package/dist/{z-schema-options.d.mts → z-schema-options.d.ts} +3 -3
- package/dist/{z-schema-options.mjs → z-schema-options.js} +4 -4
- package/dist/{z-schema-reader.d.mts → z-schema-reader.d.ts} +1 -1
- package/dist/{z-schema-versions.mjs → z-schema-versions.js} +21 -21
- package/dist/{z-schema.d.mts → z-schema.d.ts} +5 -13
- package/dist/{z-schema.mjs → z-schema.js} +37 -47
- package/package.json +22 -23
- package/src/errors.ts +1 -2
- package/src/format-validators.ts +139 -59
- package/src/json-schema-versions.ts +56 -2
- package/src/json-schema.ts +10 -9
- package/src/json-validation.ts +189 -146
- package/src/report.ts +37 -49
- package/src/schema-cache.ts +13 -13
- package/src/schema-compiler.ts +170 -117
- package/src/schema-validator.ts +239 -238
- package/src/utils/array.ts +9 -6
- package/src/utils/base64.ts +13 -2
- package/src/utils/clone.ts +28 -30
- package/src/utils/date.ts +6 -3
- package/src/utils/hostname.ts +27 -27
- package/src/utils/json.ts +16 -9
- package/src/utils/properties.ts +2 -2
- package/src/utils/schema-regex.ts +4 -4
- package/src/utils/time.ts +5 -5
- package/src/utils/unicode.ts +12 -5
- package/src/utils/what-is.ts +1 -5
- package/src/validation/array.ts +24 -22
- package/src/validation/combinators.ts +14 -14
- package/src/validation/numeric.ts +14 -28
- package/src/validation/object.ts +32 -36
- package/src/validation/ref.ts +5 -6
- package/src/validation/shared.ts +22 -21
- package/src/validation/string.ts +29 -39
- package/src/validation/type.ts +17 -17
- package/src/z-schema-base.ts +49 -38
- package/src/z-schema-options.ts +4 -3
- package/src/z-schema.ts +35 -45
- package/umd/ZSchema.js +711 -695
- package/umd/ZSchema.min.js +2 -2
- package/umd/package.json +3 -0
- package/dist/utils/unicode.mjs +0 -12
- package/dist/validation/type.mjs +0 -32
- /package/dist/{format-validators.d.mts → format-validators.d.ts} +0 -0
- /package/dist/{json-schema-versions.mjs → json-schema-versions.js} +0 -0
- /package/dist/schemas/{draft-04-schema.mjs → draft-04-schema.js} +0 -0
- /package/dist/schemas/{draft-06-schema.mjs → draft-06-schema.js} +0 -0
- /package/dist/schemas/{draft-07-schema.mjs → draft-07-schema.js} +0 -0
- /package/dist/schemas/{draft-2019-09-meta-applicator.mjs → draft-2019-09-meta-applicator.js} +0 -0
- /package/dist/schemas/{draft-2019-09-meta-content.mjs → draft-2019-09-meta-content.js} +0 -0
- /package/dist/schemas/{draft-2019-09-meta-core.mjs → draft-2019-09-meta-core.js} +0 -0
- /package/dist/schemas/{draft-2019-09-meta-format.mjs → draft-2019-09-meta-format.js} +0 -0
- /package/dist/schemas/{draft-2019-09-meta-meta-data.mjs → draft-2019-09-meta-meta-data.js} +0 -0
- /package/dist/schemas/{draft-2019-09-meta-validation.mjs → draft-2019-09-meta-validation.js} +0 -0
- /package/dist/schemas/{draft-2019-09-schema.mjs → draft-2019-09-schema.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-applicator.mjs → draft-2020-12-meta-applicator.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-content.mjs → draft-2020-12-meta-content.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-core.mjs → draft-2020-12-meta-core.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-format-annotation.mjs → draft-2020-12-meta-format-annotation.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-format-assertion.mjs → draft-2020-12-meta-format-assertion.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-meta-data.mjs → draft-2020-12-meta-meta-data.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-unevaluated.mjs → draft-2020-12-meta-unevaluated.js} +0 -0
- /package/dist/schemas/{draft-2020-12-meta-validation.mjs → draft-2020-12-meta-validation.js} +0 -0
- /package/dist/schemas/{draft-2020-12-schema.mjs → draft-2020-12-schema.js} +0 -0
- /package/dist/utils/{constants.mjs → constants.js} +0 -0
- /package/dist/utils/{date.mjs → date.js} +0 -0
- /package/dist/utils/{properties.mjs → properties.js} +0 -0
- /package/dist/utils/{symbols.mjs → symbols.js} +0 -0
- /package/dist/utils/{uri.mjs → uri.js} +0 -0
- /package/dist/{z-schema-reader.mjs → z-schema-reader.js} +0 -0
package/src/utils/array.ts
CHANGED
|
@@ -7,9 +7,11 @@ import { areEqual } from './json.js';
|
|
|
7
7
|
* Falls back to pairwise deep comparison (O(n²)) when the array contains
|
|
8
8
|
* objects or arrays that need structural equality checks.
|
|
9
9
|
*/
|
|
10
|
-
export const isUniqueArray =
|
|
10
|
+
export const isUniqueArray = (arr: unknown[], indexes?: number[], maxDepth?: number): boolean => {
|
|
11
11
|
const l = arr.length;
|
|
12
|
-
if (l <= 1)
|
|
12
|
+
if (l <= 1) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
// Fast path: if every element is a primitive, use a Set.
|
|
15
17
|
// We distinguish types so that e.g. 1 !== '1' and 0 !== false.
|
|
@@ -27,7 +29,7 @@ export const isUniqueArray = <T>(arr: T[], indexes?: number[], maxDepth?: number
|
|
|
27
29
|
const seen = new Set<string>();
|
|
28
30
|
for (let i = 0; i < l; i++) {
|
|
29
31
|
const v = arr[i];
|
|
30
|
-
const key = typeof v
|
|
32
|
+
const key = `${typeof v}:${String(v)}`;
|
|
31
33
|
if (seen.has(key)) {
|
|
32
34
|
// Find the first occurrence for the indexes report.
|
|
33
35
|
if (indexes) {
|
|
@@ -47,9 +49,10 @@ export const isUniqueArray = <T>(arr: T[], indexes?: number[], maxDepth?: number
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
// Slow path: at least one element is an object/array — need deep comparison.
|
|
52
|
+
const eqOpts = { maxDepth };
|
|
50
53
|
for (let i = 0; i < l; i++) {
|
|
51
54
|
for (let j = i + 1; j < l; j++) {
|
|
52
|
-
if (areEqual(arr[i], arr[j],
|
|
55
|
+
if (areEqual(arr[i], arr[j], eqOpts)) {
|
|
53
56
|
if (indexes) {
|
|
54
57
|
indexes.push(i, j);
|
|
55
58
|
}
|
|
@@ -60,9 +63,9 @@ export const isUniqueArray = <T>(arr: T[], indexes?: number[], maxDepth?: number
|
|
|
60
63
|
return true;
|
|
61
64
|
};
|
|
62
65
|
|
|
63
|
-
export const difference = (bigSet:
|
|
66
|
+
export const difference = <T>(bigSet: readonly T[], subSet: readonly T[]): T[] => {
|
|
64
67
|
const exclusions = new Set(subSet);
|
|
65
|
-
const arr = [];
|
|
68
|
+
const arr: T[] = [];
|
|
66
69
|
let idx = bigSet.length;
|
|
67
70
|
while (idx--) {
|
|
68
71
|
if (!exclusions.has(bigSet[idx])) {
|
package/src/utils/base64.ts
CHANGED
|
@@ -20,9 +20,20 @@ export const decodeBase64 = (value: string): string | undefined => {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Node fallback (reached only when `atob` is unavailable, i.e. not a browser).
|
|
24
|
+
// Read `Buffer` off `globalThis` rather than the bare global so it is typed
|
|
25
|
+
// without an untyped global reference; on Node >=22 `Buffer` is always present
|
|
26
|
+
// on `globalThis`. (A bundler polyfill injected as a module-scoped variable but
|
|
27
|
+
// not onto `globalThis` would be missed, but that path is unreachable in browsers
|
|
28
|
+
// where `atob` above is used instead.)
|
|
29
|
+
const bufferCtor = (
|
|
30
|
+
globalThis as {
|
|
31
|
+
Buffer?: { from(data: string, encoding: string): { toString(encoding: string): string } };
|
|
32
|
+
}
|
|
33
|
+
).Buffer;
|
|
34
|
+
if (bufferCtor !== undefined) {
|
|
24
35
|
try {
|
|
25
|
-
return
|
|
36
|
+
return bufferCtor.from(value, 'base64').toString('utf-8');
|
|
26
37
|
} catch {
|
|
27
38
|
return undefined;
|
|
28
39
|
}
|
package/src/utils/clone.ts
CHANGED
|
@@ -5,29 +5,28 @@ export const shallowClone = <T>(src: T): T => {
|
|
|
5
5
|
if (src == null || typeof src !== 'object') {
|
|
6
6
|
return src;
|
|
7
7
|
}
|
|
8
|
-
let res: any;
|
|
9
8
|
if (Array.isArray(src)) {
|
|
10
|
-
res = [];
|
|
9
|
+
const res: unknown[] = [];
|
|
11
10
|
for (let i = 0; i < src.length; i++) {
|
|
12
11
|
res[i] = src[i];
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
return res as T;
|
|
14
|
+
}
|
|
15
|
+
const res: Record<string, unknown> = {};
|
|
16
|
+
const keys = Object.keys(src).sort() as Array<keyof T>;
|
|
17
|
+
for (let i = 0; i < keys.length; i++) {
|
|
18
|
+
copyProp(src, res, keys[i]);
|
|
20
19
|
}
|
|
21
|
-
return res;
|
|
20
|
+
return res as T;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
export const deepClone = <T>(src: T, maxDepth = DEFAULT_MAX_RECURSION_DEPTH): T => {
|
|
25
24
|
let vidx = 0;
|
|
26
|
-
const visited = new Map();
|
|
27
|
-
const cloned:
|
|
28
|
-
const cloneDeepInner = <
|
|
29
|
-
if (typeof
|
|
30
|
-
return
|
|
25
|
+
const visited = new Map<unknown, number>();
|
|
26
|
+
const cloned: unknown[] = [];
|
|
27
|
+
const cloneDeepInner = <U>(node: U, _depth: number): U => {
|
|
28
|
+
if (typeof node !== 'object' || node === null) {
|
|
29
|
+
return node;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
if (_depth >= maxDepth) {
|
|
@@ -37,29 +36,28 @@ export const deepClone = <T>(src: T, maxDepth = DEFAULT_MAX_RECURSION_DEPTH): T
|
|
|
37
36
|
);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
const cidx = visited.get(src);
|
|
39
|
+
const cidx = visited.get(node);
|
|
42
40
|
|
|
43
41
|
if (cidx !== undefined) {
|
|
44
|
-
return cloned[cidx];
|
|
42
|
+
return cloned[cidx] as U;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
visited.set(
|
|
48
|
-
if (Array.isArray(
|
|
49
|
-
res = [];
|
|
45
|
+
visited.set(node, vidx++);
|
|
46
|
+
if (Array.isArray(node)) {
|
|
47
|
+
const res: unknown[] = [];
|
|
50
48
|
cloned.push(res);
|
|
51
|
-
for (let i = 0; i <
|
|
52
|
-
res[i] = cloneDeepInner(
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
res = {};
|
|
56
|
-
cloned.push(res);
|
|
57
|
-
const keys = Object.keys(src).sort() as Array<keyof T>;
|
|
58
|
-
for (const key of keys) {
|
|
59
|
-
copyProp(src, res, key, (v: any) => cloneDeepInner(v, _depth + 1));
|
|
49
|
+
for (let i = 0; i < node.length; i++) {
|
|
50
|
+
res[i] = cloneDeepInner(node[i], _depth + 1);
|
|
60
51
|
}
|
|
52
|
+
return res as U;
|
|
53
|
+
}
|
|
54
|
+
const res: Record<string, unknown> = {};
|
|
55
|
+
cloned.push(res);
|
|
56
|
+
const keys = Object.keys(node).sort() as Array<keyof U>;
|
|
57
|
+
for (let i = 0; i < keys.length; i++) {
|
|
58
|
+
copyProp(node, res, keys[i], (v: unknown) => cloneDeepInner(v, _depth + 1));
|
|
61
59
|
}
|
|
62
|
-
return res;
|
|
60
|
+
return res as U;
|
|
63
61
|
};
|
|
64
62
|
return cloneDeepInner(src, 0);
|
|
65
63
|
};
|
package/src/utils/date.ts
CHANGED
|
@@ -2,15 +2,18 @@ const isLeapYear = (year: number): boolean => year % 4 === 0 && (year % 100 !==
|
|
|
2
2
|
|
|
3
3
|
const getDaysInMonth = (year: number, month: number): number => {
|
|
4
4
|
switch (month) {
|
|
5
|
-
case 2:
|
|
5
|
+
case 2: {
|
|
6
6
|
return isLeapYear(year) ? 29 : 28;
|
|
7
|
+
}
|
|
7
8
|
case 4:
|
|
8
9
|
case 6:
|
|
9
10
|
case 9:
|
|
10
|
-
case 11:
|
|
11
|
+
case 11: {
|
|
11
12
|
return 30;
|
|
12
|
-
|
|
13
|
+
}
|
|
14
|
+
default: {
|
|
13
15
|
return 31;
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
18
|
};
|
|
16
19
|
|
package/src/utils/hostname.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import punycode from 'punycode/punycode.js';
|
|
2
2
|
import isIPModule from 'validator/lib/isIP.js';
|
|
3
3
|
|
|
4
|
-
const IDN_SEPARATOR_REGEX = /[\u3002\
|
|
5
|
-
const IDN_SEPARATOR_TEST_REGEX = /[\u3002\
|
|
4
|
+
const IDN_SEPARATOR_REGEX = /[\u3002\uFF0E\uFF61]/g;
|
|
5
|
+
const IDN_SEPARATOR_TEST_REGEX = /[\u3002\uFF0E\uFF61]/;
|
|
6
6
|
|
|
7
7
|
const splitHostnameLabels = (hostname: string): string[] | null => {
|
|
8
8
|
if (hostname.length === 0 || hostname.length > 255) {
|
|
@@ -12,8 +12,11 @@ const splitHostnameLabels = (hostname: string): string[] | null => {
|
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
14
|
const labels = hostname.split('.');
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
for (let i = 0; i < labels.length; i++) {
|
|
16
|
+
const len = labels[i].length;
|
|
17
|
+
if (len === 0 || len > 63) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
17
20
|
}
|
|
18
21
|
return labels;
|
|
19
22
|
};
|
|
@@ -33,7 +36,7 @@ const toUnicodeLabel = (label: string): string | null => {
|
|
|
33
36
|
}
|
|
34
37
|
try {
|
|
35
38
|
return punycode.toUnicode(label.toLowerCase());
|
|
36
|
-
} catch
|
|
39
|
+
} catch {
|
|
37
40
|
return null;
|
|
38
41
|
}
|
|
39
42
|
};
|
|
@@ -51,44 +54,39 @@ const isValidIdnUnicodeLabel = (label: string): boolean => {
|
|
|
51
54
|
return false;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
if (/[\
|
|
57
|
+
if (/[\u302E\u302F\u0640\u07FA]/u.test(label)) {
|
|
55
58
|
return false;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
for (let idx = 0; idx < label.length; idx++) {
|
|
59
62
|
const char = label[idx];
|
|
60
63
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
if (
|
|
65
|
+
char === '\u00B7' &&
|
|
66
|
+
(idx === 0 || idx === label.length - 1 || label[idx - 1] !== 'l' || label[idx + 1] !== 'l')
|
|
67
|
+
) {
|
|
68
|
+
return false;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
if (char === '\u0375') {
|
|
68
|
-
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
+
if (char === '\u0375' && (idx === label.length - 1 || !isGreek(label[idx + 1]))) {
|
|
72
|
+
return false;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
if (char === '\
|
|
74
|
-
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
75
|
+
if ((char === '\u05F3' || char === '\u05F4') && (idx === 0 || !isHebrew(label[idx - 1]))) {
|
|
76
|
+
return false;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
if (char === '\
|
|
80
|
-
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
79
|
+
if (char === '\u200D' && (idx === 0 || label[idx - 1] !== '\u094D')) {
|
|
80
|
+
return false;
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
if (label.includes('\
|
|
84
|
+
if (label.includes('\u30FB') && !hasCjkKanaOrHan(label.replaceAll('・', ''))) {
|
|
87
85
|
return false;
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
const hasArabicIndic = /[\u0660-\u0669]/.test(label);
|
|
91
|
-
const hasExtendedArabicIndic = /[\
|
|
89
|
+
const hasExtendedArabicIndic = /[\u06F0-\u06F9]/.test(label);
|
|
92
90
|
if (hasArabicIndic && hasExtendedArabicIndic) {
|
|
93
91
|
return false;
|
|
94
92
|
}
|
|
@@ -129,7 +127,7 @@ const isValidIdnUnicodeLabel = (label: string): boolean => {
|
|
|
129
127
|
*/
|
|
130
128
|
export const isValidHostname = (hostname: string): boolean => {
|
|
131
129
|
// eslint-disable-next-line no-control-regex
|
|
132
|
-
if (IDN_SEPARATOR_TEST_REGEX.test(hostname) || /[^\
|
|
130
|
+
if (IDN_SEPARATOR_TEST_REGEX.test(hostname) || /[^\u0000-\u007F]/.test(hostname)) {
|
|
133
131
|
return false;
|
|
134
132
|
}
|
|
135
133
|
|
|
@@ -142,7 +140,8 @@ export const isValidHostname = (hostname: string): boolean => {
|
|
|
142
140
|
return false;
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
for (
|
|
143
|
+
for (let i = 0; i < labels.length; i++) {
|
|
144
|
+
const label = labels[i];
|
|
146
145
|
if (!isAsciiHostnameLabel(label)) {
|
|
147
146
|
return false;
|
|
148
147
|
}
|
|
@@ -166,7 +165,8 @@ export const isValidIdnHostname = (hostname: string): boolean => {
|
|
|
166
165
|
return false;
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
for (
|
|
168
|
+
for (let i = 0; i < labels.length; i++) {
|
|
169
|
+
const label = labels[i];
|
|
170
170
|
const unicodeLabel = toUnicodeLabel(label);
|
|
171
171
|
if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) {
|
|
172
172
|
return false;
|
package/src/utils/json.ts
CHANGED
|
@@ -6,6 +6,8 @@ interface AreEqualOptions {
|
|
|
6
6
|
maxDepth?: number;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export const sortedKeys = (obj: Record<string, unknown>): string[] => Object.keys(obj).sort();
|
|
10
|
+
|
|
9
11
|
export const areEqual = (json1: unknown, json2: unknown, options?: AreEqualOptions, _depth = 0): boolean => {
|
|
10
12
|
const caseInsensitiveComparison = options?.caseInsensitiveComparison || false;
|
|
11
13
|
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_RECURSION_DEPTH;
|
|
@@ -21,7 +23,7 @@ export const areEqual = (json1: unknown, json2: unknown, options?: AreEqualOptio
|
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
23
25
|
if (
|
|
24
|
-
caseInsensitiveComparison
|
|
26
|
+
caseInsensitiveComparison &&
|
|
25
27
|
typeof json1 === 'string' &&
|
|
26
28
|
typeof json2 === 'string' &&
|
|
27
29
|
json1.toUpperCase() === json2.toUpperCase()
|
|
@@ -75,16 +77,21 @@ export const areEqual = (json1: unknown, json2: unknown, options?: AreEqualOptio
|
|
|
75
77
|
return false;
|
|
76
78
|
};
|
|
77
79
|
|
|
78
|
-
export const decodeJSONPointer = (str: string) =>
|
|
79
|
-
|
|
80
|
-
return decodeURIComponent(str).replace(/~[0-1]/g, (x) => (x === '~1' ? '/' : '~'));
|
|
81
|
-
};
|
|
80
|
+
export const decodeJSONPointer = (str: string) =>
|
|
81
|
+
decodeURIComponent(str).replaceAll(/~[0-1]/g, (x) => (x === '~1' ? '/' : '~'));
|
|
82
82
|
|
|
83
|
-
export const
|
|
84
|
-
|
|
85
|
-
export const get = (obj: any, path: string | Array<string | number>): any => {
|
|
83
|
+
export const get = (obj: unknown, path: string | Array<string | number>): unknown => {
|
|
86
84
|
if (typeof path === 'string') {
|
|
87
85
|
path = path.split('.');
|
|
88
86
|
}
|
|
89
|
-
|
|
87
|
+
let acc: unknown = obj;
|
|
88
|
+
for (let i = 0; i < path.length; i++) {
|
|
89
|
+
const key = path[i];
|
|
90
|
+
if (acc && (acc as Record<string | number, unknown>)[key] !== undefined) {
|
|
91
|
+
acc = (acc as Record<string | number, unknown>)[key];
|
|
92
|
+
} else {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return acc;
|
|
90
97
|
};
|
package/src/utils/properties.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export function copyProp(from: object, to: object, key: string | number | symbol, fn?:
|
|
1
|
+
export function copyProp(from: object, to: object, key: string | number | symbol, fn?: (value: unknown) => unknown) {
|
|
2
2
|
if (Object.hasOwn(from, key)) {
|
|
3
3
|
Object.defineProperty(to, key, {
|
|
4
|
-
value: fn ? fn((from as
|
|
4
|
+
value: fn ? fn((from as Record<PropertyKey, unknown>)[key]) : (from as Record<PropertyKey, unknown>)[key],
|
|
5
5
|
enumerable: true,
|
|
6
6
|
writable: true,
|
|
7
7
|
configurable: true,
|
|
@@ -40,12 +40,12 @@ export function compileSchemaRegex(
|
|
|
40
40
|
try {
|
|
41
41
|
const re = new RegExp(pattern, 'u');
|
|
42
42
|
return { ok: true, value: re };
|
|
43
|
-
} catch (
|
|
43
|
+
} catch (error: unknown) {
|
|
44
44
|
return {
|
|
45
45
|
ok: false,
|
|
46
46
|
error: {
|
|
47
47
|
pattern,
|
|
48
|
-
message:
|
|
48
|
+
message: error instanceof Error ? error.message : 'Invalid regular expression',
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
}
|
|
@@ -53,12 +53,12 @@ export function compileSchemaRegex(
|
|
|
53
53
|
try {
|
|
54
54
|
const re = new RegExp(pattern);
|
|
55
55
|
return { ok: true, value: re };
|
|
56
|
-
} catch (
|
|
56
|
+
} catch (error: unknown) {
|
|
57
57
|
return {
|
|
58
58
|
ok: false,
|
|
59
59
|
error: {
|
|
60
60
|
pattern,
|
|
61
|
-
message:
|
|
61
|
+
message: error instanceof Error ? error.message : 'Invalid regular expression',
|
|
62
62
|
},
|
|
63
63
|
};
|
|
64
64
|
}
|
package/src/utils/time.ts
CHANGED
|
@@ -32,9 +32,9 @@ export const parseRfc3339Time = (time: string): ParsedRfc3339Time | null => {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const hour = parseInt(matches[1], 10);
|
|
36
|
-
const minute = parseInt(matches[2], 10);
|
|
37
|
-
const second = parseInt(matches[3], 10);
|
|
35
|
+
const hour = Number.parseInt(matches[1], 10);
|
|
36
|
+
const minute = Number.parseInt(matches[2], 10);
|
|
37
|
+
const second = Number.parseInt(matches[3], 10);
|
|
38
38
|
if (hour > 23 || minute > 59 || second > 60) {
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
@@ -48,8 +48,8 @@ export const parseRfc3339Time = (time: string): ParsedRfc3339Time | null => {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const offsetSign = offsetMatches[1] as '+' | '-';
|
|
51
|
-
const offsetHour = parseInt(offsetMatches[2], 10);
|
|
52
|
-
const offsetMinute = parseInt(offsetMatches[3], 10);
|
|
51
|
+
const offsetHour = Number.parseInt(offsetMatches[2], 10);
|
|
52
|
+
const offsetMinute = Number.parseInt(offsetMatches[3], 10);
|
|
53
53
|
if (offsetHour > 23 || offsetMinute > 59) {
|
|
54
54
|
return null;
|
|
55
55
|
}
|
package/src/utils/unicode.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns the number of Unicode code points in the string.
|
|
3
|
-
* Uses
|
|
3
|
+
* Uses a surrogate-aware charCodeAt scan (equivalent to the string iterator)
|
|
4
|
+
* that counts a surrogate pair as one code point and lone surrogates as one each.
|
|
4
5
|
*/
|
|
5
6
|
export function unicodeLength(str: string): number {
|
|
6
|
-
let count =
|
|
7
|
-
for (
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
let count = str.length;
|
|
8
|
+
for (let i = 0; i < str.length - 1; i++) {
|
|
9
|
+
const hi = str.charCodeAt(i);
|
|
10
|
+
if (hi >= 0xd8_00 && hi <= 0xdb_ff) {
|
|
11
|
+
const lo = str.charCodeAt(i + 1);
|
|
12
|
+
if (lo >= 0xdc_00 && lo <= 0xdf_ff) {
|
|
13
|
+
count--;
|
|
14
|
+
i++;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
10
17
|
}
|
|
11
18
|
return count;
|
|
12
19
|
}
|
package/src/utils/what-is.ts
CHANGED
|
@@ -26,11 +26,7 @@ export const whatIs = (what: unknown): WHAT_IS => {
|
|
|
26
26
|
|
|
27
27
|
if (typeof what === 'number') {
|
|
28
28
|
if (Number.isFinite(what)) {
|
|
29
|
-
|
|
30
|
-
return 'integer';
|
|
31
|
-
} else {
|
|
32
|
-
return 'number';
|
|
33
|
-
}
|
|
29
|
+
return what % 1 === 0 ? 'integer' : 'number';
|
|
34
30
|
}
|
|
35
31
|
if (Number.isNaN(what)) {
|
|
36
32
|
return 'not-a-number';
|
package/src/validation/array.ts
CHANGED
|
@@ -9,9 +9,9 @@ import { cacheValidationResult, deferOrRunSync, shouldSkipValidate } from './sha
|
|
|
9
9
|
// additionalItems
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
|
|
12
|
-
export function additionalItemsValidator(
|
|
12
|
+
export function additionalItemsValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
|
|
13
13
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.1.2
|
|
14
|
-
if (shouldSkipValidate(
|
|
14
|
+
if (shouldSkipValidate(ctx.validateOptions, ['ARRAY_ADDITIONAL_ITEMS'])) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
if (!Array.isArray(json)) {
|
|
@@ -19,10 +19,8 @@ export function additionalItemsValidator(this: ZSchemaBase, report: Report, sche
|
|
|
19
19
|
}
|
|
20
20
|
// if the value of "additionalItems" is boolean value false and the value of "items" is an array,
|
|
21
21
|
// the json is valid if its size is less than, or equal to, the size of "items".
|
|
22
|
-
if (schema.additionalItems === false && Array.isArray(schema.items)) {
|
|
23
|
-
|
|
24
|
-
report.addError('ARRAY_ADDITIONAL_ITEMS', undefined, undefined, schema, 'additionalItems');
|
|
25
|
-
}
|
|
22
|
+
if (schema.additionalItems === false && Array.isArray(schema.items) && json.length > schema.items.length) {
|
|
23
|
+
report.addError('ARRAY_ADDITIONAL_ITEMS', undefined, undefined, schema, 'additionalItems');
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
26
|
|
|
@@ -47,9 +45,9 @@ export function prefixItemsValidator() {
|
|
|
47
45
|
// maxItems
|
|
48
46
|
// ---------------------------------------------------------------------------
|
|
49
47
|
|
|
50
|
-
export function maxItemsValidator(
|
|
48
|
+
export function maxItemsValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
|
|
51
49
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.2.2
|
|
52
|
-
if (shouldSkipValidate(
|
|
50
|
+
if (shouldSkipValidate(ctx.validateOptions, ['ARRAY_LENGTH_LONG'])) {
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
55
53
|
if (!Array.isArray(json)) {
|
|
@@ -64,9 +62,9 @@ export function maxItemsValidator(this: ZSchemaBase, report: Report, schema: Jso
|
|
|
64
62
|
// minItems
|
|
65
63
|
// ---------------------------------------------------------------------------
|
|
66
64
|
|
|
67
|
-
export function minItemsValidator(
|
|
65
|
+
export function minItemsValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
|
|
68
66
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.3.2
|
|
69
|
-
if (shouldSkipValidate(
|
|
67
|
+
if (shouldSkipValidate(ctx.validateOptions, ['ARRAY_LENGTH_SHORT'])) {
|
|
70
68
|
return;
|
|
71
69
|
}
|
|
72
70
|
if (!Array.isArray(json)) {
|
|
@@ -81,9 +79,9 @@ export function minItemsValidator(this: ZSchemaBase, report: Report, schema: Jso
|
|
|
81
79
|
// uniqueItems
|
|
82
80
|
// ---------------------------------------------------------------------------
|
|
83
81
|
|
|
84
|
-
export function uniqueItemsValidator(
|
|
82
|
+
export function uniqueItemsValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
|
|
85
83
|
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.3.4.2
|
|
86
|
-
if (shouldSkipValidate(
|
|
84
|
+
if (shouldSkipValidate(ctx.validateOptions, ['ARRAY_UNIQUE'])) {
|
|
87
85
|
return;
|
|
88
86
|
}
|
|
89
87
|
if (!Array.isArray(json)) {
|
|
@@ -91,7 +89,7 @@ export function uniqueItemsValidator(this: ZSchemaBase, report: Report, schema:
|
|
|
91
89
|
}
|
|
92
90
|
if (schema.uniqueItems === true) {
|
|
93
91
|
const matches: any[] = [];
|
|
94
|
-
if (isUniqueArray(json, matches,
|
|
92
|
+
if (!isUniqueArray(json, matches, ctx.options.maxRecursionDepth)) {
|
|
95
93
|
report.addError('ARRAY_UNIQUE', matches, undefined, schema, 'uniqueItems');
|
|
96
94
|
}
|
|
97
95
|
}
|
|
@@ -101,8 +99,8 @@ export function uniqueItemsValidator(this: ZSchemaBase, report: Report, schema:
|
|
|
101
99
|
// contains
|
|
102
100
|
// ---------------------------------------------------------------------------
|
|
103
101
|
|
|
104
|
-
export function containsValidator(
|
|
105
|
-
if (shouldSkipValidate(
|
|
102
|
+
export function containsValidator(ctx: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
|
|
103
|
+
if (shouldSkipValidate(ctx.validateOptions, ['CONTAINS'])) {
|
|
106
104
|
return;
|
|
107
105
|
}
|
|
108
106
|
|
|
@@ -120,19 +118,19 @@ export function containsValidator(this: ZSchemaBase, report: Report, schema: Jso
|
|
|
120
118
|
for (let idx = 0; idx < json.length; idx++) {
|
|
121
119
|
const subReport = new Report_(report);
|
|
122
120
|
subReports.push(subReport);
|
|
123
|
-
|
|
121
|
+
ctx._jsonValidate(subReport, containsSchema, json[idx]);
|
|
124
122
|
cacheValidationResult(report, containsSchema, json[idx], subReport.errors.length === 0);
|
|
125
123
|
}
|
|
126
124
|
|
|
127
125
|
const addContainsErrorIfNeeded = () => {
|
|
128
126
|
let matchingItems = 0;
|
|
129
|
-
for (
|
|
130
|
-
if (
|
|
127
|
+
for (let i = 0; i < subReports.length; i++) {
|
|
128
|
+
if (subReports[i].errors.length === 0) {
|
|
131
129
|
matchingItems += 1;
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
132
|
|
|
135
|
-
const supportsContainsBounds =
|
|
133
|
+
const supportsContainsBounds = ctx.options.version === 'draft2019-09' || ctx.options.version === 'draft2020-12';
|
|
136
134
|
const minContains: number =
|
|
137
135
|
supportsContainsBounds && typeof schema.minContains === 'number' ? (schema.minContains ?? 1) : 1;
|
|
138
136
|
const maxContains =
|
|
@@ -142,7 +140,7 @@ export function containsValidator(this: ZSchemaBase, report: Report, schema: Jso
|
|
|
142
140
|
const notTooManyMatches = maxContains === undefined || matchingItems <= maxContains;
|
|
143
141
|
|
|
144
142
|
if (!hasEnoughMatches || !notTooManyMatches) {
|
|
145
|
-
report.addError('CONTAINS', undefined, subReports, schema
|
|
143
|
+
report.addError('CONTAINS', undefined, subReports, schema);
|
|
146
144
|
}
|
|
147
145
|
};
|
|
148
146
|
|
|
@@ -153,5 +151,9 @@ export function containsValidator(this: ZSchemaBase, report: Report, schema: Jso
|
|
|
153
151
|
// maxContains / minContains (no-op — handled inside contains)
|
|
154
152
|
// ---------------------------------------------------------------------------
|
|
155
153
|
|
|
156
|
-
export function maxContainsValidator() {
|
|
157
|
-
|
|
154
|
+
export function maxContainsValidator() {
|
|
155
|
+
// no-op: maxContains is evaluated inside the `contains` validator
|
|
156
|
+
}
|
|
157
|
+
export function minContainsValidator() {
|
|
158
|
+
// no-op: minContains is evaluated inside the `contains` validator
|
|
159
|
+
}
|