z-schema 10.0.0 → 12.0.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/README.md +35 -17
- package/cjs/index.d.ts +345 -34
- package/cjs/index.js +4446 -1685
- package/dist/errors.js +5 -0
- package/dist/format-validators.js +131 -107
- package/dist/json-schema-versions.js +4 -1
- package/dist/json-schema.js +50 -16
- package/dist/json-validation.js +524 -669
- package/dist/report.js +37 -16
- package/dist/schema-cache.js +76 -18
- package/dist/schema-compiler.js +72 -47
- package/dist/schema-validator.js +117 -52
- package/dist/schemas/draft-07-schema.json +172 -0
- package/dist/schemas/draft-2019-09-meta-applicator.json +52 -0
- package/dist/schemas/draft-2019-09-meta-content.json +12 -0
- package/dist/schemas/draft-2019-09-meta-core.json +53 -0
- package/dist/schemas/draft-2019-09-meta-format.json +10 -0
- package/dist/schemas/draft-2019-09-meta-meta-data.json +32 -0
- package/dist/schemas/draft-2019-09-meta-validation.json +94 -0
- package/dist/schemas/draft-2019-09-schema.json +41 -0
- package/dist/schemas/draft-2020-12-meta-applicator.json +44 -0
- package/dist/schemas/draft-2020-12-meta-content.json +12 -0
- package/dist/schemas/draft-2020-12-meta-core.json +47 -0
- package/dist/schemas/draft-2020-12-meta-format-annotation.json +10 -0
- package/dist/schemas/draft-2020-12-meta-format-assertion.json +10 -0
- package/dist/schemas/draft-2020-12-meta-meta-data.json +32 -0
- package/dist/schemas/draft-2020-12-meta-unevaluated.json +11 -0
- package/dist/schemas/draft-2020-12-meta-validation.json +94 -0
- package/dist/schemas/draft-2020-12-schema.json +57 -0
- package/dist/types/errors.d.ts +4 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/json-schema-versions.d.ts +128 -9
- package/dist/types/json-schema.d.ts +28 -11
- package/dist/types/json-validation.d.ts +2 -3
- package/dist/types/report.d.ts +14 -4
- package/dist/types/schema-cache.d.ts +7 -0
- package/dist/types/schema-compiler.d.ts +5 -3
- package/dist/types/schema-validator.d.ts +2 -2
- package/dist/types/utils/array.d.ts +8 -1
- package/dist/types/utils/base64.d.ts +2 -0
- package/dist/types/utils/clone.d.ts +1 -1
- package/dist/types/utils/date.d.ts +1 -0
- package/dist/types/utils/hostname.d.ts +2 -0
- package/dist/types/utils/json.d.ts +2 -1
- package/dist/types/utils/properties.d.ts +0 -1
- package/dist/types/utils/time.d.ts +12 -0
- package/dist/types/utils/unicode.d.ts +3 -12
- package/dist/types/validation/array.d.ts +12 -0
- package/dist/types/validation/combinators.d.ts +10 -0
- package/dist/types/validation/numeric.d.ts +8 -0
- package/dist/types/validation/object.d.ts +13 -0
- package/dist/types/validation/ref.d.ts +11 -0
- package/dist/types/validation/shared.d.ts +26 -0
- package/dist/types/validation/string.d.ts +9 -0
- package/dist/types/validation/type.d.ts +6 -0
- package/dist/types/z-schema-base.d.ts +39 -1
- package/dist/types/z-schema-options.d.ts +3 -0
- package/dist/types/z-schema.d.ts +144 -8
- package/dist/utils/array.js +49 -7
- package/dist/utils/base64.js +29 -0
- package/dist/utils/clone.js +13 -12
- package/dist/utils/date.js +21 -0
- package/dist/utils/hostname.js +146 -0
- package/dist/utils/json.js +11 -6
- package/dist/utils/properties.js +1 -6
- package/dist/utils/time.js +50 -0
- package/dist/utils/unicode.js +8 -41
- package/dist/utils/uri.js +1 -1
- package/dist/validation/array.js +128 -0
- package/dist/validation/combinators.js +107 -0
- package/dist/validation/numeric.js +97 -0
- package/dist/validation/object.js +238 -0
- package/dist/validation/ref.js +70 -0
- package/dist/validation/shared.js +136 -0
- package/dist/validation/string.js +178 -0
- package/dist/validation/type.js +55 -0
- package/dist/z-schema-base.js +52 -32
- package/dist/z-schema-options.js +12 -8
- package/dist/z-schema-versions.js +92 -9
- package/dist/z-schema.js +135 -38
- package/package.json +22 -8
- package/src/errors.ts +8 -0
- package/src/format-validators.ts +146 -105
- package/src/index.ts +10 -1
- package/src/json-schema-versions.ts +181 -11
- package/src/json-schema.ts +102 -35
- package/src/json-validation.ts +653 -724
- package/src/report.ts +42 -20
- package/src/schema-cache.ts +94 -18
- package/src/schema-compiler.ts +94 -51
- package/src/schema-validator.ts +132 -56
- package/src/schemas/draft-07-schema.json +172 -0
- package/src/schemas/draft-2019-09-meta-applicator.json +53 -0
- package/src/schemas/draft-2019-09-meta-content.json +14 -0
- package/src/schemas/draft-2019-09-meta-core.json +54 -0
- package/src/schemas/draft-2019-09-meta-format.json +11 -0
- package/src/schemas/draft-2019-09-meta-meta-data.json +34 -0
- package/src/schemas/draft-2019-09-meta-validation.json +95 -0
- package/src/schemas/draft-2019-09-schema.json +42 -0
- package/src/schemas/draft-2020-12-meta-applicator.json +45 -0
- package/src/schemas/draft-2020-12-meta-content.json +14 -0
- package/src/schemas/draft-2020-12-meta-core.json +48 -0
- package/src/schemas/draft-2020-12-meta-format-annotation.json +11 -0
- package/src/schemas/draft-2020-12-meta-format-assertion.json +11 -0
- package/src/schemas/draft-2020-12-meta-meta-data.json +34 -0
- package/src/schemas/draft-2020-12-meta-unevaluated.json +12 -0
- package/src/schemas/draft-2020-12-meta-validation.json +95 -0
- package/src/schemas/draft-2020-12-schema.json +58 -0
- package/src/utils/array.ts +51 -7
- package/src/utils/base64.ts +32 -0
- package/src/utils/clone.ts +16 -12
- package/src/utils/date.ts +23 -0
- package/src/utils/hostname.ts +174 -0
- package/src/utils/json.ts +15 -6
- package/src/utils/properties.ts +1 -7
- package/src/utils/time.ts +73 -0
- package/src/utils/unicode.ts +8 -39
- package/src/utils/uri.ts +1 -1
- package/src/validation/array.ts +158 -0
- package/src/validation/combinators.ts +132 -0
- package/src/validation/numeric.ts +120 -0
- package/src/validation/object.ts +318 -0
- package/src/validation/ref.ts +85 -0
- package/src/validation/shared.ts +191 -0
- package/src/validation/string.ts +224 -0
- package/src/validation/type.ts +66 -0
- package/src/z-schema-base.ts +54 -36
- package/src/z-schema-options.ts +15 -8
- package/src/z-schema-versions.ts +107 -12
- package/src/z-schema.ts +158 -42
- package/umd/ZSchema.js +4446 -1685
- package/umd/ZSchema.min.js +1 -1
- package/dist/schemas/draft-04-hyper-schema.json +0 -135
- package/dist/schemas/draft-06-hyper-schema.json +0 -132
- package/dist/schemas/draft-06-links.json +0 -43
- package/src/schemas/draft-04-hyper-schema.json +0 -136
- package/src/schemas/draft-06-hyper-schema.json +0 -133
- package/src/schemas/draft-06-links.json +0 -43
package/dist/errors.js
CHANGED
|
@@ -12,6 +12,7 @@ export const Errors = {
|
|
|
12
12
|
ARRAY_LENGTH_LONG: 'Array is too long ({0}), maximum {1}',
|
|
13
13
|
ARRAY_UNIQUE: 'Array items are not unique (indexes {0} and {1})',
|
|
14
14
|
ARRAY_ADDITIONAL_ITEMS: 'Additional items not allowed',
|
|
15
|
+
ARRAY_UNEVALUATED_ITEMS: 'Unevaluated items are not allowed',
|
|
15
16
|
// Numeric errors
|
|
16
17
|
MULTIPLE_OF: 'Value {0} is not a multiple of {1}',
|
|
17
18
|
MINIMUM: 'Value {0} is less than minimum {1}',
|
|
@@ -23,6 +24,7 @@ export const Errors = {
|
|
|
23
24
|
OBJECT_PROPERTIES_MAXIMUM: 'Too many properties defined ({0}), maximum {1}',
|
|
24
25
|
OBJECT_MISSING_REQUIRED_PROPERTY: 'Missing required property: {0}',
|
|
25
26
|
OBJECT_ADDITIONAL_PROPERTIES: 'Additional properties not allowed: {0}',
|
|
27
|
+
OBJECT_UNEVALUATED_PROPERTIES: 'Unevaluated properties are not allowed: {0}',
|
|
26
28
|
OBJECT_DEPENDENCY_KEY: 'Dependency failed - key must exist: {0} (due to key: {1})',
|
|
27
29
|
// String errors
|
|
28
30
|
MIN_LENGTH: 'String is too short ({0} chars), minimum {1}',
|
|
@@ -52,6 +54,9 @@ export const Errors = {
|
|
|
52
54
|
CONST: 'Value does not match const: {0}',
|
|
53
55
|
CONTAINS: 'Array does not contain an item matching the schema',
|
|
54
56
|
PROPERTY_NAMES: 'Property name {0} does not match the propertyNames schema',
|
|
57
|
+
// Draft-2019-09+ errors
|
|
58
|
+
COLLECT_EVALUATED_DEPTH_EXCEEDED: 'Schema nesting depth exceeded maximum ({0}) during unevaluated items/properties collection',
|
|
59
|
+
MAX_RECURSION_DEPTH_EXCEEDED: 'Maximum recursion depth ({0}) exceeded. If your schema or data is deeply nested and valid, increase the maxRecursionDepth option.',
|
|
55
60
|
};
|
|
56
61
|
export class ValidateError extends Error {
|
|
57
62
|
name;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import isEmailModule from 'validator/lib/isEmail.js';
|
|
2
2
|
import isIPModule from 'validator/lib/isIP.js';
|
|
3
3
|
import isURLModule from 'validator/lib/isURL.js';
|
|
4
|
+
import { isValidRfc3339Date } from './utils/date.js';
|
|
5
|
+
import { isValidHostname, isValidIdnHostname } from './utils/hostname.js';
|
|
4
6
|
import { sortedKeys } from './utils/json.js';
|
|
7
|
+
import { parseRfc3339Time } from './utils/time.js';
|
|
5
8
|
const dateValidator = (date) => {
|
|
6
9
|
if (typeof date !== 'string') {
|
|
7
10
|
return true;
|
|
@@ -11,13 +14,10 @@ const dateValidator = (date) => {
|
|
|
11
14
|
if (matches === null) {
|
|
12
15
|
return false;
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
return true;
|
|
17
|
+
const year = parseInt(matches[1], 10);
|
|
18
|
+
const month = parseInt(matches[2], 10);
|
|
19
|
+
const day = parseInt(matches[3], 10);
|
|
20
|
+
return isValidRfc3339Date(year, month, day);
|
|
21
21
|
};
|
|
22
22
|
const dateTimeValidator = (dateTime) => {
|
|
23
23
|
if (typeof dateTime !== 'string') {
|
|
@@ -38,111 +38,34 @@ const dateTimeValidator = (dateTime) => {
|
|
|
38
38
|
const year = parseInt(dateMatches[1], 10);
|
|
39
39
|
const month = parseInt(dateMatches[2], 10);
|
|
40
40
|
const day = parseInt(dateMatches[3], 10);
|
|
41
|
-
if (
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
// Check if date is valid
|
|
45
|
-
const date = new Date(year, month - 1, day);
|
|
46
|
-
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
// Check time
|
|
50
|
-
const timeMatches = /^([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$/.exec(timePart);
|
|
51
|
-
if (timeMatches === null) {
|
|
41
|
+
if (!isValidRfc3339Date(year, month, day)) {
|
|
52
42
|
return false;
|
|
53
43
|
}
|
|
54
|
-
|
|
55
|
-
const minute = parseInt(timeMatches[2], 10);
|
|
56
|
-
const second = parseInt(timeMatches[3], 10);
|
|
57
|
-
if (hour > 23 || minute > 59 || second > 60) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
// Check offset
|
|
61
|
-
let utcHour = hour;
|
|
62
|
-
if (timeMatches[5] !== 'z') {
|
|
63
|
-
const offset = timeMatches[5];
|
|
64
|
-
const offsetMatches = /^([+-])([0-9]{2}):([0-9]{2})$/.exec(offset);
|
|
65
|
-
if (offsetMatches === null) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
const offsetSign = offsetMatches[1];
|
|
69
|
-
const offsetHour = parseInt(offsetMatches[2], 10);
|
|
70
|
-
const offsetMinute = parseInt(offsetMatches[3], 10);
|
|
71
|
-
if (offsetHour > 23 || offsetMinute > 59) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
if (offsetSign === '+') {
|
|
75
|
-
utcHour = hour - offsetHour;
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
utcHour = hour + offsetHour;
|
|
79
|
-
}
|
|
80
|
-
utcHour = ((utcHour % 24) + 24) % 24;
|
|
81
|
-
}
|
|
82
|
-
// Leap second only at 23:59:60 UTC
|
|
83
|
-
if (second === 60) {
|
|
84
|
-
if (utcHour !== 23 || minute !== 59) {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return true;
|
|
44
|
+
return parseRfc3339Time(timePart) !== null;
|
|
89
45
|
};
|
|
90
46
|
const emailValidator = (email) => {
|
|
91
47
|
if (typeof email !== 'string') {
|
|
92
48
|
return true;
|
|
93
49
|
}
|
|
94
|
-
|
|
50
|
+
if (isEmailModule.default(email, { require_tld: true, allow_ip_domain: true })) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const ipv6Literal = /^(.+)@\[IPv6:([^\]]+)\]$/i.exec(email);
|
|
54
|
+
if (!ipv6Literal) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const localPart = ipv6Literal[1];
|
|
58
|
+
const addressPart = ipv6Literal[2];
|
|
59
|
+
if (!isIPModule.default(addressPart, 6)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return isEmailModule.default(`${localPart}@example.com`, { require_tld: true });
|
|
95
63
|
};
|
|
96
64
|
const hostnameValidator = (hostname) => {
|
|
97
65
|
if (typeof hostname !== 'string') {
|
|
98
66
|
return true;
|
|
99
67
|
}
|
|
100
|
-
|
|
101
|
-
http://json-schema.org/latest/json-schema-validation.html#anchor114
|
|
102
|
-
A string instance is valid against this attribute if it is a valid
|
|
103
|
-
representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
|
|
104
|
-
|
|
105
|
-
http://tools.ietf.org/html/rfc1034#section-3.5
|
|
106
|
-
|
|
107
|
-
<digit> ::= any one of the ten digits 0 through 9
|
|
108
|
-
var digit = /[0-9]/;
|
|
109
|
-
|
|
110
|
-
<letter> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
|
|
111
|
-
var letter = /[a-zA-Z]/;
|
|
112
|
-
|
|
113
|
-
<let-dig> ::= <letter> | <digit>
|
|
114
|
-
var letDig = /[0-9a-zA-Z]/;
|
|
115
|
-
|
|
116
|
-
<let-dig-hyp> ::= <let-dig> | "-"
|
|
117
|
-
var letDigHyp = /[-0-9a-zA-Z]/;
|
|
118
|
-
|
|
119
|
-
<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
|
|
120
|
-
var ldhStr = /[-0-9a-zA-Z]+/;
|
|
121
|
-
|
|
122
|
-
<label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
|
|
123
|
-
var label = /[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?/;
|
|
124
|
-
|
|
125
|
-
<subdomain> ::= <label> | <subdomain> "." <label>
|
|
126
|
-
var subdomain = /^[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?(\.[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?)*$/;
|
|
127
|
-
|
|
128
|
-
<domain> ::= <subdomain> | " "
|
|
129
|
-
var domain = null;
|
|
130
|
-
*/
|
|
131
|
-
const valid = /^[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?(\.[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?)*$/.test(hostname);
|
|
132
|
-
if (valid) {
|
|
133
|
-
// the sum of all label octets and label lengths is limited to 255.
|
|
134
|
-
if (hostname.length > 255) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
// Each node has a label, which is zero to 63 octets in length
|
|
138
|
-
const labels = hostname.split('.');
|
|
139
|
-
for (let i = 0; i < labels.length; i++) {
|
|
140
|
-
if (labels[i].length > 63) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return valid;
|
|
68
|
+
return isValidHostname(hostname);
|
|
146
69
|
};
|
|
147
70
|
const ipv4Validator = (ipv4) => {
|
|
148
71
|
if (typeof ipv4 !== 'string') {
|
|
@@ -161,7 +84,21 @@ const ipv6Validator = (ipv6) => {
|
|
|
161
84
|
};
|
|
162
85
|
const regexValidator = (input) => {
|
|
163
86
|
if (typeof input !== 'string') {
|
|
164
|
-
return
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
const invalidEscapes = new Set(['a']);
|
|
90
|
+
for (let idx = 0; idx < input.length; idx++) {
|
|
91
|
+
if (input[idx] !== '\\') {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
idx++;
|
|
95
|
+
if (idx >= input.length) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const escaped = input[idx];
|
|
99
|
+
if (invalidEscapes.has(escaped)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
165
102
|
}
|
|
166
103
|
try {
|
|
167
104
|
RegExp(input);
|
|
@@ -171,6 +108,55 @@ const regexValidator = (input) => {
|
|
|
171
108
|
return false;
|
|
172
109
|
}
|
|
173
110
|
};
|
|
111
|
+
const durationValidator = (input) => {
|
|
112
|
+
if (typeof input !== 'string') {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
// eslint-disable-next-line no-control-regex
|
|
116
|
+
if (!/^P[\x00-\x7F]*$/.test(input)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (!input.startsWith('P')) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const body = input.slice(1);
|
|
123
|
+
if (body.length === 0) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (body.includes('W')) {
|
|
127
|
+
return /^\d+W$/.test(body);
|
|
128
|
+
}
|
|
129
|
+
const parts = body.split('T');
|
|
130
|
+
if (parts.length > 2) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const datePart = parts[0];
|
|
134
|
+
const timePart = parts.length === 2 ? parts[1] : undefined;
|
|
135
|
+
if (!/^(?:\d+Y)?(?:\d+M)?(?:\d+D)?$/.test(datePart)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const hasDateComponent = /\d+[YMD]/.test(datePart);
|
|
139
|
+
let hasTimeComponent = false;
|
|
140
|
+
if (timePart !== undefined) {
|
|
141
|
+
if (timePart.length === 0) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (!/^(?:\d+H)?(?:\d+M)?(?:\d+S)?$/.test(timePart)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
hasTimeComponent = /\d+[HMS]/.test(timePart);
|
|
148
|
+
if (!hasTimeComponent) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return hasDateComponent || hasTimeComponent;
|
|
153
|
+
};
|
|
154
|
+
const uuidValidator = (input) => {
|
|
155
|
+
if (typeof input !== 'string') {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(input);
|
|
159
|
+
};
|
|
174
160
|
const strictUriValidator = (uri) => typeof uri !== 'string' || isURLModule.default(uri);
|
|
175
161
|
const uriValidator = function (uri) {
|
|
176
162
|
if (typeof uri !== 'string')
|
|
@@ -235,14 +221,14 @@ const jsonPointerValidator = (pointer) => {
|
|
|
235
221
|
const relativeJsonPointerValidator = (pointer) => {
|
|
236
222
|
if (typeof pointer !== 'string')
|
|
237
223
|
return true;
|
|
238
|
-
// Relative JSON Pointer:
|
|
239
|
-
|
|
224
|
+
// Relative JSON Pointer: non-negative integer prefix (no leading zeros unless zero),
|
|
225
|
+
// followed by either '#', a JSON Pointer, or nothing.
|
|
226
|
+
return /^(?:0|[1-9]\d*)(?:#|(?:\/(?:[^~]|~0|~1)*)+)?$/.test(pointer);
|
|
240
227
|
};
|
|
241
228
|
const timeValidator = (time) => {
|
|
242
229
|
if (typeof time !== 'string')
|
|
243
230
|
return true;
|
|
244
|
-
|
|
245
|
-
return /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(\.\d+)?$/.test(time);
|
|
231
|
+
return parseRfc3339Time(time) !== null;
|
|
246
232
|
};
|
|
247
233
|
const idnEmailValidator = (email) => {
|
|
248
234
|
if (typeof email !== 'string')
|
|
@@ -250,6 +236,30 @@ const idnEmailValidator = (email) => {
|
|
|
250
236
|
// Simple email check, allowing international chars
|
|
251
237
|
return /^[^\s@]+@[^\s@]+$/.test(email);
|
|
252
238
|
};
|
|
239
|
+
const idnHostnameValidator = (hostname) => {
|
|
240
|
+
if (typeof hostname !== 'string')
|
|
241
|
+
return true;
|
|
242
|
+
return isValidIdnHostname(hostname);
|
|
243
|
+
};
|
|
244
|
+
const iriValidator = (iri) => {
|
|
245
|
+
if (typeof iri !== 'string')
|
|
246
|
+
return true;
|
|
247
|
+
if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:[^"\\<>^{}^`| ]*$/u.test(iri)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
new URL(iri);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
catch (_e) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const iriReferenceValidator = (iriReference) => {
|
|
259
|
+
if (typeof iriReference !== 'string')
|
|
260
|
+
return true;
|
|
261
|
+
return /^([a-zA-Z][a-zA-Z0-9+.-]*:)?[^"\\<>^{}^`| ]*$/u.test(iriReference);
|
|
262
|
+
};
|
|
253
263
|
const inbuiltValidators = {
|
|
254
264
|
date: dateValidator,
|
|
255
265
|
'date-time': dateTimeValidator,
|
|
@@ -267,6 +277,11 @@ const inbuiltValidators = {
|
|
|
267
277
|
'relative-json-pointer': relativeJsonPointerValidator,
|
|
268
278
|
time: timeValidator,
|
|
269
279
|
'idn-email': idnEmailValidator,
|
|
280
|
+
'idn-hostname': idnHostnameValidator,
|
|
281
|
+
iri: iriValidator,
|
|
282
|
+
'iri-reference': iriReferenceValidator,
|
|
283
|
+
duration: durationValidator,
|
|
284
|
+
uuid: uuidValidator,
|
|
270
285
|
};
|
|
271
286
|
const customValidators = {};
|
|
272
287
|
export function getFormatValidators(options) {
|
|
@@ -293,8 +308,17 @@ export function getSupportedFormats(customFormats) {
|
|
|
293
308
|
return keys.filter((key) => merged[key] != null);
|
|
294
309
|
}
|
|
295
310
|
export function isFormatSupported(name, customFormats) {
|
|
296
|
-
|
|
297
|
-
|
|
311
|
+
if (customFormats) {
|
|
312
|
+
const custom = customFormats[name];
|
|
313
|
+
// Explicitly null means unregistered at instance level
|
|
314
|
+
if (custom === null)
|
|
315
|
+
return false;
|
|
316
|
+
if (custom != null)
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
if (name in customValidators)
|
|
320
|
+
return customValidators[name] != null;
|
|
321
|
+
return name in inbuiltValidators;
|
|
298
322
|
}
|
|
299
323
|
export function getRegisteredFormats() {
|
|
300
324
|
return sortedKeys(customValidators);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export const CURRENT_DEFAULT_SCHEMA_VERSION = '
|
|
1
|
+
export const CURRENT_DEFAULT_SCHEMA_VERSION = 'draft2020-12';
|
|
2
2
|
export const VERSION_SCHEMA_URL_MAPPING = {
|
|
3
3
|
'draft-04': 'http://json-schema.org/draft-04/schema#',
|
|
4
4
|
'draft-06': 'http://json-schema.org/draft-06/schema#',
|
|
5
|
+
'draft-07': 'http://json-schema.org/draft-07/schema#',
|
|
6
|
+
'draft2019-09': 'https://json-schema.org/draft/2019-09/schema',
|
|
7
|
+
'draft2020-12': 'https://json-schema.org/draft/2020-12/schema',
|
|
5
8
|
};
|
package/dist/json-schema.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
import { getRemotePath, isAbsoluteUri } from './utils/uri.js';
|
|
2
|
+
/**
|
|
3
|
+
* Keywords whose values are not JSON Schema sub-schemas and must not be
|
|
4
|
+
* traversed during schema walking (id collection, reference collection, etc.).
|
|
5
|
+
*/
|
|
6
|
+
export const NON_SCHEMA_KEYWORDS = ['enum', 'const', 'default', 'examples'];
|
|
7
|
+
/** Returns true if the key is an internal z-schema property (prefixed with `__$`). */
|
|
8
|
+
export const isInternalKey = (key) => key.startsWith('__$');
|
|
1
9
|
import { isObject } from './utils/what-is.js';
|
|
10
|
+
import { DEFAULT_MAX_RECURSION_DEPTH } from './z-schema-options.js';
|
|
2
11
|
export const getId = (schema) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
if (schema.id) {
|
|
12
|
+
// Draft-04 uses `id` exclusively — never return `$id` for a draft-04 schema
|
|
13
|
+
if (typeof schema.$schema === 'string' && schema.$schema.includes('draft-04')) {
|
|
7
14
|
return schema.id;
|
|
8
15
|
}
|
|
9
|
-
|
|
16
|
+
// Draft-06+ uses `$id`; fall back to `id` for backward compatibility
|
|
17
|
+
// with schemas that haven't migrated to `$id` yet.
|
|
18
|
+
return schema.$id ?? schema.id;
|
|
10
19
|
};
|
|
11
|
-
export const findId = (schema, id) => {
|
|
20
|
+
export const findId = (schema, id, targetBaseUri, currentBaseUri, maxDepth = DEFAULT_MAX_RECURSION_DEPTH, _depth = 0) => {
|
|
12
21
|
// process only arrays and objects
|
|
13
22
|
if (typeof schema !== 'object' || schema === null) {
|
|
14
23
|
return;
|
|
@@ -17,17 +26,39 @@ export const findId = (schema, id) => {
|
|
|
17
26
|
if (!id) {
|
|
18
27
|
return schema;
|
|
19
28
|
}
|
|
29
|
+
if (_depth >= maxDepth) {
|
|
30
|
+
throw new Error(`Maximum recursion depth (${maxDepth}) exceeded in findId. ` +
|
|
31
|
+
'If your schema is deeply nested and valid, increase the maxRecursionDepth option.');
|
|
32
|
+
}
|
|
33
|
+
const baseUri = currentBaseUri ?? targetBaseUri;
|
|
20
34
|
const schemaId = getId(schema);
|
|
35
|
+
let nextBaseUri = baseUri;
|
|
21
36
|
if (schemaId) {
|
|
22
|
-
if (
|
|
37
|
+
if (isAbsoluteUri(schemaId)) {
|
|
38
|
+
nextBaseUri = getRemotePath(schemaId);
|
|
39
|
+
}
|
|
40
|
+
else if (baseUri && isAbsoluteUri(baseUri)) {
|
|
41
|
+
try {
|
|
42
|
+
nextBaseUri = getRemotePath(new URL(schemaId, baseUri).toString());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// keep existing scope when URL resolution fails
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const inTargetBase = !targetBaseUri || nextBaseUri === targetBaseUri;
|
|
50
|
+
if (inTargetBase) {
|
|
51
|
+
if (schemaId && (schemaId === id || (schemaId[0] === '#' && schemaId.substring(1) === id))) {
|
|
52
|
+
return schema;
|
|
53
|
+
}
|
|
54
|
+
if (schema.$anchor === id || schema.$dynamicAnchor === id) {
|
|
23
55
|
return schema;
|
|
24
56
|
}
|
|
25
57
|
}
|
|
26
|
-
let
|
|
58
|
+
let result;
|
|
27
59
|
if (Array.isArray(schema)) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
result = findId(schema[idx], id);
|
|
60
|
+
for (let i = 0; i < schema.length; i++) {
|
|
61
|
+
result = findId(schema[i], id, targetBaseUri, nextBaseUri, maxDepth, _depth + 1);
|
|
31
62
|
if (result) {
|
|
32
63
|
return result;
|
|
33
64
|
}
|
|
@@ -35,13 +66,16 @@ export const findId = (schema, id) => {
|
|
|
35
66
|
}
|
|
36
67
|
if (isObject(schema)) {
|
|
37
68
|
const keys = Object.keys(schema);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
69
|
+
// Reverse iteration: when sibling sub-schemas share the same $dynamicAnchor
|
|
70
|
+
// name, the LAST sibling (by key order) must win. This is required for
|
|
71
|
+
// $dynamicRef correctness — e.g. the optional test "$dynamicRef skips over
|
|
72
|
+
// intermediate resources - pointer reference across resource boundary".
|
|
73
|
+
for (let i = keys.length - 1; i >= 0; i--) {
|
|
74
|
+
const k = keys[i];
|
|
75
|
+
if (isInternalKey(k) || NON_SCHEMA_KEYWORDS.includes(k)) {
|
|
42
76
|
continue;
|
|
43
77
|
}
|
|
44
|
-
result = findId(schema[k], id);
|
|
78
|
+
result = findId(schema[k], id, targetBaseUri, nextBaseUri, maxDepth, _depth + 1);
|
|
45
79
|
if (result) {
|
|
46
80
|
return result;
|
|
47
81
|
}
|