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/schema-compiler.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type { JsonSchemaInternal } from './json-schema-versions.js';
|
|
2
2
|
import type { ZSchemaBase } from './z-schema-base.js';
|
|
3
3
|
|
|
4
|
-
import { getId, isInternalKey,
|
|
4
|
+
import { getId, isInternalKey, NON_SCHEMA_KEYWORDS_SET } from './json-schema.js';
|
|
5
5
|
import { Report } from './report.js';
|
|
6
6
|
import { DEFAULT_MAX_RECURSION_DEPTH } from './utils/constants.js';
|
|
7
7
|
import { getRemotePath, isAbsoluteUri } from './utils/uri.js';
|
|
8
8
|
import { getSchemaReader } from './z-schema-reader.js';
|
|
9
9
|
|
|
10
|
-
const UNSAFE_TARGETS = [
|
|
10
|
+
const UNSAFE_TARGETS = new Set<Record<string, unknown>>([
|
|
11
11
|
Object.prototype as unknown as Record<string, unknown>,
|
|
12
12
|
Function.prototype as unknown as Record<string, unknown>,
|
|
13
13
|
Array.prototype as unknown as Record<string, unknown>,
|
|
14
|
-
];
|
|
14
|
+
]);
|
|
15
15
|
|
|
16
16
|
/** Returns true if `obj` is a built-in prototype that must not be mutated. */
|
|
17
17
|
function isUnsafeTarget(obj: Record<string, unknown>): boolean {
|
|
18
|
-
return UNSAFE_TARGETS.
|
|
18
|
+
return UNSAFE_TARGETS.has(obj);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/** Safely assign a property on `obj`, refusing prototype-polluting keys. */
|
|
@@ -47,10 +47,77 @@ interface Id {
|
|
|
47
47
|
absoluteUri?: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const resolveReference = (base: string | undefined, ref: string) => {
|
|
51
|
+
if (isAbsoluteUri(ref)) {
|
|
52
|
+
return ref;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const baseStr = base ?? '';
|
|
56
|
+
|
|
57
|
+
if (ref.startsWith('#')) {
|
|
58
|
+
const hashIndex = baseStr.indexOf('#');
|
|
59
|
+
const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
|
|
60
|
+
return baseNoFrag + ref;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!baseStr) {
|
|
64
|
+
return ref;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hashIndex = baseStr.indexOf('#');
|
|
68
|
+
const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
|
|
69
|
+
|
|
70
|
+
if (isAbsoluteUri(baseNoFrag)) {
|
|
71
|
+
try {
|
|
72
|
+
return new URL(ref, baseNoFrag).toString();
|
|
73
|
+
} catch {
|
|
74
|
+
// fall back to manual resolution below
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let baseDir = baseNoFrag;
|
|
79
|
+
if (!baseDir.endsWith('/')) {
|
|
80
|
+
const lastSlash = baseDir.lastIndexOf('/');
|
|
81
|
+
baseDir = lastSlash === -1 ? '' : baseDir.slice(0, lastSlash + 1);
|
|
82
|
+
}
|
|
83
|
+
return baseDir + ref;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const isSimpleIdentifier = (id: string) =>
|
|
87
|
+
!id.startsWith('#') && !id.includes('/') && !id.includes('.') && !id.includes('#');
|
|
88
|
+
|
|
89
|
+
const resolveIdScope = (base: string | undefined, id: string) => {
|
|
90
|
+
if (isAbsoluteUri(id)) {
|
|
91
|
+
return id;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const baseStr = base ?? '';
|
|
95
|
+
|
|
96
|
+
// Treat simple identifiers (no '/', '.', or '#') as same-document fragment ids
|
|
97
|
+
if (isSimpleIdentifier(id)) {
|
|
98
|
+
const hashIndex = baseStr.indexOf('#');
|
|
99
|
+
const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
|
|
100
|
+
return `${baseNoFrag}#${id}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return resolveReference(base, id);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const resolveSchemaScopeId = (base: string | undefined, schema: JsonSchemaInternal, id: string) => {
|
|
107
|
+
if (typeof schema.$id === 'string') {
|
|
108
|
+
return resolveReference(base, id);
|
|
109
|
+
}
|
|
110
|
+
return resolveIdScope(base, id);
|
|
111
|
+
};
|
|
112
|
+
|
|
50
113
|
export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECURSION_DEPTH) => {
|
|
51
114
|
const ids: Id[] = [];
|
|
52
115
|
function walk(node: any, scope: Id[], _depth = 0) {
|
|
53
|
-
if (typeof node !== 'object' || node == null)
|
|
116
|
+
if (typeof node !== 'object' || node == null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const schemaNode = node as JsonSchemaInternal;
|
|
54
121
|
|
|
55
122
|
if (_depth >= maxDepth) {
|
|
56
123
|
throw new Error(
|
|
@@ -61,7 +128,7 @@ export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECUR
|
|
|
61
128
|
|
|
62
129
|
let addedScope = false;
|
|
63
130
|
|
|
64
|
-
const nodeId = getId(
|
|
131
|
+
const nodeId = getId(schemaNode);
|
|
65
132
|
if (typeof nodeId === 'string') {
|
|
66
133
|
let type: Id['type'] = isAbsoluteUri(nodeId) ? 'absolute' : 'relative';
|
|
67
134
|
if (scope.length === 0) {
|
|
@@ -70,19 +137,30 @@ export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECUR
|
|
|
70
137
|
const id: Id = {
|
|
71
138
|
id: nodeId,
|
|
72
139
|
type,
|
|
73
|
-
obj:
|
|
140
|
+
obj: schemaNode,
|
|
74
141
|
};
|
|
75
142
|
if (type === 'absolute' || (type === 'root' && isAbsoluteUri(nodeId))) {
|
|
76
143
|
id.absoluteUri = nodeId;
|
|
77
|
-
} else if (
|
|
78
|
-
|
|
144
|
+
} else if (
|
|
145
|
+
type === 'root' &&
|
|
146
|
+
typeof schemaNode.id === 'string' &&
|
|
147
|
+
isAbsoluteUri(schemaNode.id) &&
|
|
148
|
+
schemaNode.id !== nodeId
|
|
149
|
+
) {
|
|
150
|
+
id.absoluteUri = resolveSchemaScopeId(schemaNode.id, schemaNode, nodeId);
|
|
79
151
|
} else if (type === 'relative') {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
152
|
+
let absoluteParent: Id | undefined;
|
|
153
|
+
for (let i = scope.length - 1; i >= 0; i--) {
|
|
154
|
+
const sc = scope[i];
|
|
155
|
+
if (sc.type === 'absolute' || (sc.type === 'root' && sc.absoluteUri)) {
|
|
156
|
+
absoluteParent = sc;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
id.absoluteParent = absoluteParent;
|
|
83
161
|
if (id.absoluteParent) {
|
|
84
162
|
const parentUri = id.absoluteParent.absoluteUri || id.absoluteParent.id;
|
|
85
|
-
id.absoluteUri = resolveSchemaScopeId(parentUri,
|
|
163
|
+
id.absoluteUri = resolveSchemaScopeId(parentUri, schemaNode, id.id);
|
|
86
164
|
}
|
|
87
165
|
}
|
|
88
166
|
ids.push(id);
|
|
@@ -95,9 +173,11 @@ export const collectIds = (obj: JsonSchemaInternal, maxDepth = DEFAULT_MAX_RECUR
|
|
|
95
173
|
walk(item, scope, _depth + 1);
|
|
96
174
|
}
|
|
97
175
|
} else {
|
|
98
|
-
for (const key of Object.keys(
|
|
99
|
-
if (isInternalKey(key) ||
|
|
100
|
-
|
|
176
|
+
for (const key of Object.keys(schemaNode)) {
|
|
177
|
+
if (isInternalKey(key) || NON_SCHEMA_KEYWORDS_SET.has(key)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
walk((schemaNode as Record<string, unknown>)[key], scope, _depth + 1);
|
|
101
181
|
}
|
|
102
182
|
}
|
|
103
183
|
|
|
@@ -125,10 +205,10 @@ export const collectReferences = (
|
|
|
125
205
|
maxDepth = DEFAULT_MAX_RECURSION_DEPTH,
|
|
126
206
|
_depth = 0
|
|
127
207
|
) => {
|
|
128
|
-
results
|
|
129
|
-
scope
|
|
130
|
-
path
|
|
131
|
-
options
|
|
208
|
+
results ||= [];
|
|
209
|
+
scope ||= [];
|
|
210
|
+
path ||= [];
|
|
211
|
+
options ||= {};
|
|
132
212
|
|
|
133
213
|
if (typeof obj !== 'object' || obj === null) {
|
|
134
214
|
return results;
|
|
@@ -141,7 +221,7 @@ export const collectReferences = (
|
|
|
141
221
|
);
|
|
142
222
|
}
|
|
143
223
|
|
|
144
|
-
const hasRef = typeof obj.$ref === 'string' &&
|
|
224
|
+
const hasRef = typeof obj.$ref === 'string' && obj.__$refResolved === undefined;
|
|
145
225
|
let addedScope = false;
|
|
146
226
|
const isRootScope = scope.length === 0;
|
|
147
227
|
const objId = getId(obj);
|
|
@@ -151,40 +231,40 @@ export const collectReferences = (
|
|
|
151
231
|
}
|
|
152
232
|
|
|
153
233
|
if (typeof scopeId === 'string' && (isRootScope || !hasRef || options.useRefObjectScope === true)) {
|
|
154
|
-
const base = scope.length > 0 ? scope
|
|
234
|
+
const base = scope.length > 0 ? scope.at(-1) : undefined;
|
|
155
235
|
scope.push(resolveSchemaScopeId(base, obj, scopeId));
|
|
156
236
|
addedScope = true;
|
|
157
237
|
}
|
|
158
238
|
|
|
159
239
|
if (hasRef) {
|
|
160
240
|
results.push({
|
|
161
|
-
ref: resolveReference(scope
|
|
241
|
+
ref: resolveReference(scope.at(-1), obj.$ref!),
|
|
162
242
|
key: '$ref',
|
|
163
|
-
obj
|
|
243
|
+
obj,
|
|
164
244
|
path: path.slice(0),
|
|
165
245
|
});
|
|
166
246
|
}
|
|
167
|
-
if (typeof obj.$recursiveRef === 'string' &&
|
|
247
|
+
if (typeof obj.$recursiveRef === 'string' && obj.__$recursiveRefResolved === undefined) {
|
|
168
248
|
results.push({
|
|
169
|
-
ref: resolveReference(scope
|
|
249
|
+
ref: resolveReference(scope.at(-1), obj.$recursiveRef),
|
|
170
250
|
key: '$recursiveRef',
|
|
171
|
-
obj
|
|
251
|
+
obj,
|
|
172
252
|
path: path.slice(0),
|
|
173
253
|
});
|
|
174
254
|
}
|
|
175
|
-
if (typeof obj.$dynamicRef === 'string' &&
|
|
255
|
+
if (typeof obj.$dynamicRef === 'string' && obj.__$dynamicRefResolved === undefined) {
|
|
176
256
|
results.push({
|
|
177
|
-
ref: resolveReference(scope
|
|
257
|
+
ref: resolveReference(scope.at(-1), obj.$dynamicRef),
|
|
178
258
|
key: '$dynamicRef',
|
|
179
|
-
obj
|
|
259
|
+
obj,
|
|
180
260
|
path: path.slice(0),
|
|
181
261
|
});
|
|
182
262
|
}
|
|
183
|
-
if (typeof obj.$schema === 'string' &&
|
|
263
|
+
if (typeof obj.$schema === 'string' && obj.__$schemaResolved === undefined) {
|
|
184
264
|
results.push({
|
|
185
|
-
ref: resolveReference(scope
|
|
265
|
+
ref: resolveReference(scope.at(-1), obj.$schema),
|
|
186
266
|
key: '$schema',
|
|
187
|
-
obj
|
|
267
|
+
obj,
|
|
188
268
|
path: path.slice(0),
|
|
189
269
|
});
|
|
190
270
|
}
|
|
@@ -192,18 +272,26 @@ export const collectReferences = (
|
|
|
192
272
|
if (Array.isArray(obj)) {
|
|
193
273
|
for (let i = 0; i < obj.length; i++) {
|
|
194
274
|
path.push(i);
|
|
195
|
-
collectReferences(obj[i], results, scope, path, options, maxDepth, _depth + 1);
|
|
275
|
+
collectReferences(obj[i] as JsonSchemaInternal, results, scope, path, options, maxDepth, _depth + 1);
|
|
196
276
|
path.pop();
|
|
197
277
|
}
|
|
198
278
|
} else {
|
|
199
279
|
const keys = Object.keys(obj);
|
|
200
280
|
for (const key of keys) {
|
|
201
281
|
// do not recurse through resolved references and other z-schema props
|
|
202
|
-
if (isInternalKey(key) ||
|
|
282
|
+
if (isInternalKey(key) || NON_SCHEMA_KEYWORDS_SET.has(key)) {
|
|
203
283
|
continue;
|
|
204
284
|
}
|
|
205
285
|
path.push(key);
|
|
206
|
-
collectReferences(
|
|
286
|
+
collectReferences(
|
|
287
|
+
(obj as Record<string, JsonSchemaInternal>)[key],
|
|
288
|
+
results,
|
|
289
|
+
scope,
|
|
290
|
+
path,
|
|
291
|
+
options,
|
|
292
|
+
maxDepth,
|
|
293
|
+
_depth + 1
|
|
294
|
+
);
|
|
207
295
|
path.pop();
|
|
208
296
|
}
|
|
209
297
|
}
|
|
@@ -215,70 +303,12 @@ export const collectReferences = (
|
|
|
215
303
|
return results;
|
|
216
304
|
};
|
|
217
305
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return ref;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const baseStr = base ?? '';
|
|
224
|
-
|
|
225
|
-
if (ref[0] === '#') {
|
|
226
|
-
const hashIndex = baseStr.indexOf('#');
|
|
227
|
-
const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
|
|
228
|
-
return baseNoFrag + ref;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!baseStr) {
|
|
232
|
-
return ref;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const hashIndex = baseStr.indexOf('#');
|
|
236
|
-
const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
|
|
237
|
-
|
|
238
|
-
if (isAbsoluteUri(baseNoFrag)) {
|
|
239
|
-
try {
|
|
240
|
-
return new URL(ref, baseNoFrag).toString();
|
|
241
|
-
} catch {
|
|
242
|
-
// fall back to manual resolution below
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
let baseDir = baseNoFrag;
|
|
247
|
-
if (!baseDir.endsWith('/')) {
|
|
248
|
-
const lastSlash = baseDir.lastIndexOf('/');
|
|
249
|
-
baseDir = lastSlash === -1 ? '' : baseDir.slice(0, lastSlash + 1);
|
|
250
|
-
}
|
|
251
|
-
return baseDir + ref;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const isSimpleIdentifier = (id: string) => id[0] !== '#' && !id.includes('/') && !id.includes('.') && !id.includes('#');
|
|
255
|
-
|
|
256
|
-
const resolveIdScope = (base: string | undefined, id: string) => {
|
|
257
|
-
if (isAbsoluteUri(id)) {
|
|
258
|
-
return id;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const baseStr = base ?? '';
|
|
262
|
-
|
|
263
|
-
// Treat simple identifiers (no '/', '.', or '#') as same-document fragment ids
|
|
264
|
-
if (isSimpleIdentifier(id)) {
|
|
265
|
-
const hashIndex = baseStr.indexOf('#');
|
|
266
|
-
const baseNoFrag = hashIndex === -1 ? baseStr : baseStr.slice(0, hashIndex);
|
|
267
|
-
return baseNoFrag + '#' + id;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return resolveReference(base, id);
|
|
271
|
-
};
|
|
306
|
+
export class SchemaCompiler {
|
|
307
|
+
private readonly validator: ZSchemaBase;
|
|
272
308
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return resolveReference(base, id);
|
|
309
|
+
constructor(validator: ZSchemaBase) {
|
|
310
|
+
this.validator = validator;
|
|
276
311
|
}
|
|
277
|
-
return resolveIdScope(base, id);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
export class SchemaCompiler {
|
|
281
|
-
constructor(private validator: ZSchemaBase) {}
|
|
282
312
|
|
|
283
313
|
collectAndCacheIds(schema: JsonSchemaInternal) {
|
|
284
314
|
const ids = collectIds(schema, this.validator.options.maxRecursionDepth);
|
|
@@ -303,7 +333,7 @@ export class SchemaCompiler {
|
|
|
303
333
|
// if schema is a string, assume it's a uri
|
|
304
334
|
if (typeof schema === 'string') {
|
|
305
335
|
const loadedSchema = this.validator.scache.getSchemaByUri(report, schema);
|
|
306
|
-
if (
|
|
336
|
+
if (loadedSchema === undefined) {
|
|
307
337
|
report.addError('SCHEMA_NOT_REACHABLE', [schema]);
|
|
308
338
|
return false;
|
|
309
339
|
}
|
|
@@ -313,15 +343,16 @@ export class SchemaCompiler {
|
|
|
313
343
|
// if schema is an array, assume it's an array of schemas
|
|
314
344
|
if (Array.isArray(schema)) {
|
|
315
345
|
if (!options?.noCache) {
|
|
316
|
-
|
|
346
|
+
for (let i = 0; i < schema.length; i++) {
|
|
347
|
+
this.collectAndCacheIds(schema[i]);
|
|
348
|
+
}
|
|
317
349
|
}
|
|
318
350
|
return this.compileArrayOfSchemas(report, schema);
|
|
319
351
|
} else if (typeof schema === 'boolean') {
|
|
320
352
|
return true;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
353
|
+
}
|
|
354
|
+
if (!options?.noCache) {
|
|
355
|
+
this.collectAndCacheIds(schema);
|
|
325
356
|
}
|
|
326
357
|
|
|
327
358
|
const canMutateSchemaObject =
|
|
@@ -334,7 +365,7 @@ export class SchemaCompiler {
|
|
|
334
365
|
canMutateSchemaObject &&
|
|
335
366
|
schema.__$compiled &&
|
|
336
367
|
schema.id &&
|
|
337
|
-
this.validator.scache.checkCacheForUri(schema.id)
|
|
368
|
+
!this.validator.scache.checkCacheForUri(schema.id)
|
|
338
369
|
) {
|
|
339
370
|
schema.__$compiled = undefined;
|
|
340
371
|
}
|
|
@@ -383,7 +414,7 @@ export class SchemaCompiler {
|
|
|
383
414
|
let response = this.validator.scache.getSchemaByUri(report, refObj.ref, schema);
|
|
384
415
|
|
|
385
416
|
// we can try to use custom schemaReader if available
|
|
386
|
-
if (
|
|
417
|
+
if (response === undefined) {
|
|
387
418
|
const schemaReader = getSchemaReader();
|
|
388
419
|
if (schemaReader) {
|
|
389
420
|
const remotePath = getRemotePath(refObj.ref);
|
|
@@ -395,17 +426,19 @@ export class SchemaCompiler {
|
|
|
395
426
|
(s as JsonSchemaInternal).id = remotePath;
|
|
396
427
|
// try to compile the schema
|
|
397
428
|
const subreport = new Report(report);
|
|
398
|
-
if (
|
|
399
|
-
// copy errors to report
|
|
400
|
-
report.errors = report.errors.concat(subreport.errors);
|
|
401
|
-
} else {
|
|
429
|
+
if (this.compileSchema(subreport, s)) {
|
|
402
430
|
response = this.validator.scache.getSchemaByUri(report, refObj.ref, schema);
|
|
431
|
+
} else {
|
|
432
|
+
// copy errors to report
|
|
433
|
+
for (let i = 0; i < subreport.errors.length; i++) {
|
|
434
|
+
report.errors.push(subreport.errors[i]);
|
|
435
|
+
}
|
|
403
436
|
}
|
|
404
437
|
}
|
|
405
438
|
}
|
|
406
439
|
}
|
|
407
440
|
|
|
408
|
-
if (
|
|
441
|
+
if (response === undefined) {
|
|
409
442
|
const hasNotValid = report.hasError('REMOTE_NOT_VALID', [refObj.ref]);
|
|
410
443
|
const isAbsolute = isAbsoluteUri(refObj.ref);
|
|
411
444
|
let isDownloaded = false;
|
|
@@ -424,9 +457,12 @@ export class SchemaCompiler {
|
|
|
424
457
|
} else if (isDownloaded) {
|
|
425
458
|
// remote is downloaded, so no UNRESOLVABLE_REFERENCE
|
|
426
459
|
} else {
|
|
427
|
-
|
|
460
|
+
const pathLen = refObj.path.length;
|
|
461
|
+
for (let i = 0; i < pathLen; i++) {
|
|
462
|
+
report.path.push(refObj.path[i]);
|
|
463
|
+
}
|
|
428
464
|
report.addError('UNRESOLVABLE_REFERENCE', [refObj.ref]);
|
|
429
|
-
report.path
|
|
465
|
+
report.path.length -= pathLen;
|
|
430
466
|
|
|
431
467
|
// publish unresolved references out
|
|
432
468
|
if (
|
|
@@ -434,7 +470,7 @@ export class SchemaCompiler {
|
|
|
434
470
|
canMutateSchemaObject &&
|
|
435
471
|
!isUnsafeTarget(schema as unknown as Record<string, unknown>)
|
|
436
472
|
) {
|
|
437
|
-
schema.__$missingReferences
|
|
473
|
+
schema.__$missingReferences ||= [];
|
|
438
474
|
schema.__$missingReferences.push(refObj);
|
|
439
475
|
}
|
|
440
476
|
}
|
|
@@ -469,7 +505,13 @@ export class SchemaCompiler {
|
|
|
469
505
|
|
|
470
506
|
do {
|
|
471
507
|
// remove all UNRESOLVABLE_REFERENCE errors before compiling array again
|
|
472
|
-
|
|
508
|
+
let wi = 0;
|
|
509
|
+
for (let ri = 0; ri < report.errors.length; ri++) {
|
|
510
|
+
if (report.errors[ri].code !== 'UNRESOLVABLE_REFERENCE') {
|
|
511
|
+
report.errors[wi++] = report.errors[ri];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
report.errors.length = wi;
|
|
473
515
|
|
|
474
516
|
// remember how many were compiled in the last loop
|
|
475
517
|
lastLoopCompiled = compiled;
|
|
@@ -478,11 +520,20 @@ export class SchemaCompiler {
|
|
|
478
520
|
compiled = this.compileArrayOfSchemasLoop(report, arr);
|
|
479
521
|
|
|
480
522
|
// fix __$missingReferences if possible
|
|
523
|
+
// Keep the FIRST schema per id to match the prior `arr.find(x => x.id === ref)`
|
|
524
|
+
// semantics (first match wins) when an array contains duplicate ids.
|
|
525
|
+
const idMap = new Map<string, JsonSchemaInternal>();
|
|
526
|
+
for (let i = 0; i < arr.length; i++) {
|
|
527
|
+
const schemaId = arr[i].id;
|
|
528
|
+
if (schemaId && !idMap.has(schemaId)) {
|
|
529
|
+
idMap.set(schemaId, arr[i]);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
481
532
|
for (const sch of arr) {
|
|
482
533
|
if (sch.__$missingReferences) {
|
|
483
534
|
for (let idx2 = sch.__$missingReferences.length - 1; idx2 >= 0; idx2--) {
|
|
484
535
|
const refObj = sch.__$missingReferences[idx2];
|
|
485
|
-
const response =
|
|
536
|
+
const response = idMap.get(refObj.ref);
|
|
486
537
|
if (response) {
|
|
487
538
|
// this might create circular references
|
|
488
539
|
safeSetProperty(refObj.obj as unknown as Record<string, unknown>, `__${refObj.key}Resolved`, response);
|
|
@@ -514,7 +565,9 @@ export class SchemaCompiler {
|
|
|
514
565
|
}
|
|
515
566
|
|
|
516
567
|
// copy errors to report
|
|
517
|
-
|
|
568
|
+
for (let i = 0; i < report.errors.length; i++) {
|
|
569
|
+
mainReport.errors.push(report.errors[i]);
|
|
570
|
+
}
|
|
518
571
|
}
|
|
519
572
|
|
|
520
573
|
return compiledCount;
|