@schema-ts/core 0.1.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 +9 -0
- package/dist/index.cjs +2529 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +412 -0
- package/dist/index.d.ts +412 -0
- package/dist/index.js +2505 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2529 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/util.ts
|
|
4
|
+
function matchSchemaType(value, type) {
|
|
5
|
+
switch (type) {
|
|
6
|
+
case "string":
|
|
7
|
+
return typeof value === "string";
|
|
8
|
+
case "number":
|
|
9
|
+
return typeof value === "number" || typeof value === "bigint";
|
|
10
|
+
case "integer":
|
|
11
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
12
|
+
case "boolean":
|
|
13
|
+
return typeof value === "boolean";
|
|
14
|
+
case "object":
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
|
+
case "array":
|
|
17
|
+
return Array.isArray(value);
|
|
18
|
+
case "null":
|
|
19
|
+
return value === null || value === void 0;
|
|
20
|
+
default:
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function detectSchemaType(value) {
|
|
25
|
+
if (value === null || value === void 0) {
|
|
26
|
+
return "null";
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return "array";
|
|
30
|
+
}
|
|
31
|
+
const type = typeof value;
|
|
32
|
+
if (type === "string") {
|
|
33
|
+
return "string";
|
|
34
|
+
}
|
|
35
|
+
if (type === "boolean") {
|
|
36
|
+
return "boolean";
|
|
37
|
+
}
|
|
38
|
+
if (type === "number") {
|
|
39
|
+
return Number.isInteger(value) ? "integer" : "number";
|
|
40
|
+
}
|
|
41
|
+
if (type === "bigint") {
|
|
42
|
+
return "number";
|
|
43
|
+
}
|
|
44
|
+
if (type === "object") {
|
|
45
|
+
return "object";
|
|
46
|
+
}
|
|
47
|
+
return "unknown";
|
|
48
|
+
}
|
|
49
|
+
function parseJsonPointer(jsonPointer) {
|
|
50
|
+
if (jsonPointer === "") return [];
|
|
51
|
+
const pointer = jsonPointer.charAt(0) === "/" ? jsonPointer.substring(1) : jsonPointer;
|
|
52
|
+
return pointer.split("/").map((t) => jsonPointerUnescape(decodeURIComponent(t)));
|
|
53
|
+
}
|
|
54
|
+
function getJsonPointer(obj, jsonPointer) {
|
|
55
|
+
return get(obj, parseJsonPointer(jsonPointer));
|
|
56
|
+
}
|
|
57
|
+
function removeJsonPointer(obj, jsonPointer) {
|
|
58
|
+
const path = parseJsonPointer(jsonPointer);
|
|
59
|
+
if (path.length === 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (obj === null || obj === void 0) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
let current = obj;
|
|
66
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
67
|
+
const segment = path[i];
|
|
68
|
+
if (Array.isArray(current)) {
|
|
69
|
+
const idx = Number(segment);
|
|
70
|
+
if (isNaN(idx) || idx < 0 || idx >= current.length) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
current = current[idx];
|
|
74
|
+
} else if (typeof current === "object" && current !== null) {
|
|
75
|
+
if (!Object.hasOwn(current, segment)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
current = current[segment];
|
|
79
|
+
} else {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (current === null || current === void 0) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const lastSegment = path[path.length - 1];
|
|
87
|
+
if (Array.isArray(current)) {
|
|
88
|
+
const idx = Number(lastSegment);
|
|
89
|
+
if (isNaN(idx) || idx < 0 || idx >= current.length) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
current.splice(idx, 1);
|
|
93
|
+
return true;
|
|
94
|
+
} else if (typeof current === "object" && current !== null) {
|
|
95
|
+
if (Object.hasOwn(current, lastSegment)) {
|
|
96
|
+
delete current[lastSegment];
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function setJsonPointer(obj, jsonPointer, value) {
|
|
103
|
+
const path = parseJsonPointer(jsonPointer);
|
|
104
|
+
if (path.length === 0) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (obj === null || obj === void 0) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
let current = obj;
|
|
111
|
+
for (let i = 0; i < path.length; i++) {
|
|
112
|
+
const segment = path[i];
|
|
113
|
+
const isLast = i === path.length - 1;
|
|
114
|
+
if (Array.isArray(current)) {
|
|
115
|
+
const idx = Number(segment);
|
|
116
|
+
if (isNaN(idx) || idx < 0) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (idx >= current.length) {
|
|
120
|
+
for (let k = current.length; k <= idx; k++) current.push(void 0);
|
|
121
|
+
}
|
|
122
|
+
if (isLast) {
|
|
123
|
+
current[idx] = value;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (current[idx] === null || current[idx] === void 0 || typeof current[idx] !== "object") {
|
|
127
|
+
const nextToken = path[i + 1];
|
|
128
|
+
const nextIdx = Number(nextToken);
|
|
129
|
+
current[idx] = !isNaN(nextIdx) ? [] : {};
|
|
130
|
+
}
|
|
131
|
+
current = current[idx];
|
|
132
|
+
} else if (typeof current === "object" && current !== null) {
|
|
133
|
+
if (isLast) {
|
|
134
|
+
current[segment] = value;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (!Object.hasOwn(current, segment) || current[segment] === void 0) {
|
|
138
|
+
const nextToken = path[i + 1];
|
|
139
|
+
const nextIdx = Number(nextToken);
|
|
140
|
+
current[segment] = !isNaN(nextIdx) ? [] : {};
|
|
141
|
+
}
|
|
142
|
+
current = current[segment];
|
|
143
|
+
} else {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
function get(obj, path) {
|
|
150
|
+
let current = obj;
|
|
151
|
+
for (const segment of path) {
|
|
152
|
+
if (Array.isArray(current)) {
|
|
153
|
+
const currentIndex = Number(segment);
|
|
154
|
+
if (isNaN(currentIndex) || currentIndex < 0 || currentIndex >= current.length) {
|
|
155
|
+
return void 0;
|
|
156
|
+
} else {
|
|
157
|
+
current = current[currentIndex];
|
|
158
|
+
}
|
|
159
|
+
} else if (typeof current === "object" && current !== null) {
|
|
160
|
+
if (!Object.hasOwn(current, segment)) {
|
|
161
|
+
return void 0;
|
|
162
|
+
} else {
|
|
163
|
+
current = current[segment];
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
function deepEqual(a, b) {
|
|
172
|
+
if (a === b) return true;
|
|
173
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (a instanceof Date && b instanceof Date) {
|
|
177
|
+
return a.getTime() === b.getTime();
|
|
178
|
+
}
|
|
179
|
+
if (a instanceof Date || b instanceof Date) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
183
|
+
return a.source === b.source && a.flags === b.flags;
|
|
184
|
+
}
|
|
185
|
+
if (a instanceof RegExp || b instanceof RegExp) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
if (a instanceof Map && b instanceof Map) {
|
|
189
|
+
if (a.size !== b.size) return false;
|
|
190
|
+
for (const [key, value] of a) {
|
|
191
|
+
if (!b.has(key) || !deepEqual(value, b.get(key))) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (a instanceof Map || b instanceof Map) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
if (a instanceof Set && b instanceof Set) {
|
|
201
|
+
if (a.size !== b.size) return false;
|
|
202
|
+
for (const value of a) {
|
|
203
|
+
let found = false;
|
|
204
|
+
for (const bValue of b) {
|
|
205
|
+
if (deepEqual(value, bValue)) {
|
|
206
|
+
found = true;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!found) return false;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (a instanceof Set || b instanceof Set) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
218
|
+
if (Array.isArray(a)) {
|
|
219
|
+
const arrA = a;
|
|
220
|
+
const arrB = b;
|
|
221
|
+
if (arrA.length !== arrB.length) return false;
|
|
222
|
+
for (let i = 0; i < arrA.length; i++) {
|
|
223
|
+
if (!deepEqual(arrA[i], arrB[i])) return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
const keysA = Object.keys(a);
|
|
228
|
+
const keysB = Object.keys(b);
|
|
229
|
+
if (keysA.length !== keysB.length) return false;
|
|
230
|
+
for (const key of keysA) {
|
|
231
|
+
if (!Object.hasOwn(b, key)) return false;
|
|
232
|
+
if (!deepEqual(
|
|
233
|
+
a[key],
|
|
234
|
+
b[key]
|
|
235
|
+
)) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
function jsonPointerEscape(str) {
|
|
242
|
+
return str.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
243
|
+
}
|
|
244
|
+
function jsonPointerUnescape(str) {
|
|
245
|
+
return str.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
246
|
+
}
|
|
247
|
+
function jsonPointerJoin(base, token) {
|
|
248
|
+
if (base === "") return "/" + jsonPointerEscape(token);
|
|
249
|
+
else return base + "/" + jsonPointerEscape(token);
|
|
250
|
+
}
|
|
251
|
+
function resolveAbsolutePath(nodePath, relativePath) {
|
|
252
|
+
if (relativePath.startsWith("/")) {
|
|
253
|
+
return nodePath === "" ? relativePath : nodePath + relativePath;
|
|
254
|
+
}
|
|
255
|
+
return relativePath;
|
|
256
|
+
}
|
|
257
|
+
var MAX_REGEX_CACHE_SIZE = 1e3;
|
|
258
|
+
var regexCache = /* @__PURE__ */ new Map();
|
|
259
|
+
function safeRegexTest(pattern, value) {
|
|
260
|
+
let regex = regexCache.get(pattern);
|
|
261
|
+
if (regex === void 0) {
|
|
262
|
+
try {
|
|
263
|
+
regex = new RegExp(pattern);
|
|
264
|
+
if (regexCache.size >= MAX_REGEX_CACHE_SIZE) {
|
|
265
|
+
const firstKey = regexCache.keys().next().value;
|
|
266
|
+
if (firstKey !== void 0) {
|
|
267
|
+
regexCache.delete(firstKey);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
regexCache.set(pattern, regex);
|
|
271
|
+
} catch {
|
|
272
|
+
regexCache.set(pattern, null);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (regex === null) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return regex.test(value);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/stringformat.ts
|
|
283
|
+
var DATE_RE = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
|
|
284
|
+
var DURATION_RE = /^P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$/;
|
|
285
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
286
|
+
var HOSTNAME_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
|
287
|
+
var IPV4_RE = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
288
|
+
var IPV6_RE = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(([0-9a-fA-F]{1,4}:){1,7}:)|(([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})|(([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2})|(([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3})|(([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4})|(([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5})|([0-9a-fA-F]{1,4}:)((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9])?[0-9]))$/;
|
|
289
|
+
var RFC3339_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
290
|
+
var UUID_RE = /^[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}$/;
|
|
291
|
+
var StringFormatValidator = class {
|
|
292
|
+
validate(format, value) {
|
|
293
|
+
switch (format) {
|
|
294
|
+
case "date-time":
|
|
295
|
+
return this.isDateTime(value);
|
|
296
|
+
case "date":
|
|
297
|
+
return this.isDate(value);
|
|
298
|
+
case "email":
|
|
299
|
+
return this.isEmail(value);
|
|
300
|
+
case "hostname":
|
|
301
|
+
return this.isHostname(value);
|
|
302
|
+
case "ipv4":
|
|
303
|
+
return this.isIPv4(value);
|
|
304
|
+
case "ipv6":
|
|
305
|
+
return this.isIPv6(value);
|
|
306
|
+
case "uri":
|
|
307
|
+
return this.isUri(value);
|
|
308
|
+
case "uuid":
|
|
309
|
+
return this.isUuid(value);
|
|
310
|
+
case "duration":
|
|
311
|
+
return this.isDuration(value);
|
|
312
|
+
default:
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
isDateTime(value) {
|
|
317
|
+
if (!RFC3339_RE.test(value)) return false;
|
|
318
|
+
return !isNaN(Date.parse(value));
|
|
319
|
+
}
|
|
320
|
+
isDate(value) {
|
|
321
|
+
const m = DATE_RE.exec(value);
|
|
322
|
+
if (!m) return false;
|
|
323
|
+
const year = Number(m[1]), month = Number(m[2]), day = Number(m[3]), daysInMonth = [
|
|
324
|
+
31,
|
|
325
|
+
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
|
|
326
|
+
31,
|
|
327
|
+
30,
|
|
328
|
+
31,
|
|
329
|
+
30,
|
|
330
|
+
31,
|
|
331
|
+
31,
|
|
332
|
+
30,
|
|
333
|
+
31,
|
|
334
|
+
30,
|
|
335
|
+
31
|
|
336
|
+
];
|
|
337
|
+
return day <= daysInMonth[month - 1];
|
|
338
|
+
}
|
|
339
|
+
isEmail(value) {
|
|
340
|
+
return EMAIL_RE.test(value);
|
|
341
|
+
}
|
|
342
|
+
isHostname(value) {
|
|
343
|
+
return HOSTNAME_RE.test(value);
|
|
344
|
+
}
|
|
345
|
+
isIPv4(value) {
|
|
346
|
+
return IPV4_RE.test(value);
|
|
347
|
+
}
|
|
348
|
+
isIPv6(value) {
|
|
349
|
+
return IPV6_RE.test(value);
|
|
350
|
+
}
|
|
351
|
+
isUri(value) {
|
|
352
|
+
try {
|
|
353
|
+
const u = new URL(value);
|
|
354
|
+
return typeof u.protocol === "string" && u.protocol.length > 0;
|
|
355
|
+
} catch {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
isUuid(value) {
|
|
360
|
+
return UUID_RE.test(value);
|
|
361
|
+
}
|
|
362
|
+
isDuration(value) {
|
|
363
|
+
return DURATION_RE.test(value) && value !== "P";
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
var stringFormatValidator = new StringFormatValidator();
|
|
367
|
+
|
|
368
|
+
// src/i18n.ts
|
|
369
|
+
var MESSAGES = {
|
|
370
|
+
"validation.anyOf": "must match at least one schema",
|
|
371
|
+
"validation.oneOf": "must match exactly one schema, matched {count}",
|
|
372
|
+
"validation.not": "must not match schema",
|
|
373
|
+
"validation.const": "must be equal to {value}",
|
|
374
|
+
"validation.enum": "must be equal to one of the allowed values",
|
|
375
|
+
"validation.type": "must be {expected}",
|
|
376
|
+
"validation.maximum": "must be <= {value}",
|
|
377
|
+
"validation.exclusiveMaximum": "must be < {value}",
|
|
378
|
+
"validation.minimum": "must be >= {value}",
|
|
379
|
+
"validation.exclusiveMinimum": "must be > {value}",
|
|
380
|
+
"validation.multipleOf": "must be multiple of {value}",
|
|
381
|
+
"validation.maxLength": "must be shorter than or equal to {value} characters",
|
|
382
|
+
"validation.minLength": "must be longer than or equal to {value} characters",
|
|
383
|
+
"validation.pattern": 'must match pattern "{pattern}"',
|
|
384
|
+
"validation.format": 'must match format "{format}"',
|
|
385
|
+
"validation.maxItems": "must have at most {value} items",
|
|
386
|
+
"validation.minItems": "must have at least {value} items",
|
|
387
|
+
"validation.uniqueItems": "must not contain duplicate items",
|
|
388
|
+
"validation.contains": "must contain at least one valid item",
|
|
389
|
+
"validation.minContains": "must contain at least {value} valid items",
|
|
390
|
+
"validation.maxContains": "must contain at most {value} valid items",
|
|
391
|
+
"validation.maxProperties": "must have at most {value} properties",
|
|
392
|
+
"validation.minProperties": "must have at least {value} properties",
|
|
393
|
+
"validation.required": "must have required property '{property}'",
|
|
394
|
+
"validation.dependentRequired": "property '{source}' requires property '{target}'",
|
|
395
|
+
"validation.additionalProperties": "must not have additional properties: {properties}",
|
|
396
|
+
"validation.failed": "validation failed"
|
|
397
|
+
};
|
|
398
|
+
var defaultErrorFormatter = (msg) => {
|
|
399
|
+
const template = MESSAGES[msg.key] ?? msg.key;
|
|
400
|
+
if (!msg.params) return template;
|
|
401
|
+
let result = template;
|
|
402
|
+
for (const [k, v] of Object.entries(msg.params)) {
|
|
403
|
+
const value = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
|
|
404
|
+
result = result.replace(`{${k}}`, value);
|
|
405
|
+
}
|
|
406
|
+
return result;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/version.ts
|
|
410
|
+
var DRAFT_URIS = {
|
|
411
|
+
"draft-04": [
|
|
412
|
+
"http://json-schema.org/draft-04/schema#",
|
|
413
|
+
"http://json-schema.org/draft-04/schema"
|
|
414
|
+
],
|
|
415
|
+
"draft-07": [
|
|
416
|
+
"http://json-schema.org/draft-07/schema#",
|
|
417
|
+
"http://json-schema.org/draft-07/schema"
|
|
418
|
+
],
|
|
419
|
+
"draft-2019-09": [
|
|
420
|
+
"https://json-schema.org/draft/2019-09/schema",
|
|
421
|
+
"https://json-schema.org/draft/2019-09/schema#"
|
|
422
|
+
],
|
|
423
|
+
"draft-2020-12": [
|
|
424
|
+
"https://json-schema.org/draft/2020-12/schema",
|
|
425
|
+
"https://json-schema.org/draft/2020-12/schema#"
|
|
426
|
+
]
|
|
427
|
+
};
|
|
428
|
+
function detectSchemaDraft(schema) {
|
|
429
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
430
|
+
return "draft-2020-12";
|
|
431
|
+
}
|
|
432
|
+
const s = schema;
|
|
433
|
+
if (s.$schema && typeof s.$schema === "string") {
|
|
434
|
+
for (const [draft, uris] of Object.entries(DRAFT_URIS)) {
|
|
435
|
+
if (uris.some((uri) => s.$schema === uri)) {
|
|
436
|
+
return draft;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if ("prefixItems" in s) {
|
|
441
|
+
return "draft-2020-12";
|
|
442
|
+
}
|
|
443
|
+
if ("$recursiveRef" in s || "$recursiveAnchor" in s || "unevaluatedProperties" in s || "unevaluatedItems" in s) {
|
|
444
|
+
return "draft-2019-09";
|
|
445
|
+
}
|
|
446
|
+
if ("id" in s && !("$id" in s)) {
|
|
447
|
+
return "draft-04";
|
|
448
|
+
}
|
|
449
|
+
if (typeof s.exclusiveMaximum === "boolean" || typeof s.exclusiveMinimum === "boolean") {
|
|
450
|
+
return "draft-04";
|
|
451
|
+
}
|
|
452
|
+
if ("dependencies" in s) {
|
|
453
|
+
if ("$id" in s) {
|
|
454
|
+
return "draft-07";
|
|
455
|
+
}
|
|
456
|
+
return "draft-04";
|
|
457
|
+
}
|
|
458
|
+
if ("additionalItems" in s) {
|
|
459
|
+
if ("$id" in s) {
|
|
460
|
+
return "draft-07";
|
|
461
|
+
}
|
|
462
|
+
return "draft-04";
|
|
463
|
+
}
|
|
464
|
+
return "draft-2020-12";
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/normalize.ts
|
|
468
|
+
function normalizeSchema(schema, options = {}) {
|
|
469
|
+
if (typeof schema === "boolean") {
|
|
470
|
+
return schema ? {} : { not: {} };
|
|
471
|
+
}
|
|
472
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
473
|
+
return {};
|
|
474
|
+
}
|
|
475
|
+
const schemaObj = schema;
|
|
476
|
+
const sourceDraft = options.sourceDraft ?? detectSchemaDraft(schemaObj);
|
|
477
|
+
const normalized = { ...schemaObj };
|
|
478
|
+
switch (sourceDraft) {
|
|
479
|
+
case "draft-04":
|
|
480
|
+
normalizeDraft04(normalized);
|
|
481
|
+
normalizeDraft07(normalized);
|
|
482
|
+
normalizeDraft201909(normalized);
|
|
483
|
+
break;
|
|
484
|
+
case "draft-07":
|
|
485
|
+
normalizeDraft07(normalized);
|
|
486
|
+
normalizeDraft201909(normalized);
|
|
487
|
+
break;
|
|
488
|
+
case "draft-2019-09":
|
|
489
|
+
normalizeDraft07(normalized);
|
|
490
|
+
normalizeDraft201909(normalized);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
normalizeGeneral(normalized);
|
|
494
|
+
normalizeNestedSchemas(normalized, options);
|
|
495
|
+
if (normalized.$schema) {
|
|
496
|
+
normalized.$schema = "https://json-schema.org/draft/2020-12/schema";
|
|
497
|
+
}
|
|
498
|
+
return normalized;
|
|
499
|
+
}
|
|
500
|
+
function normalizeDraft04(schema) {
|
|
501
|
+
const legacy = schema;
|
|
502
|
+
if ("id" in legacy && !("$id" in schema)) {
|
|
503
|
+
schema.$id = legacy.id;
|
|
504
|
+
delete legacy.id;
|
|
505
|
+
}
|
|
506
|
+
if ("$ref" in schema) {
|
|
507
|
+
for (const key of Object.keys(schema)) {
|
|
508
|
+
if (key !== "$ref" && key !== "$schema" && key !== "id" && key !== "$id" && key !== "$comment") {
|
|
509
|
+
delete legacy[key];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (typeof legacy.exclusiveMaximum === "boolean" && legacy.exclusiveMaximum === true) {
|
|
514
|
+
if (schema.maximum !== void 0) {
|
|
515
|
+
schema.exclusiveMaximum = schema.maximum;
|
|
516
|
+
delete schema.maximum;
|
|
517
|
+
} else {
|
|
518
|
+
delete legacy.exclusiveMaximum;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (typeof legacy.exclusiveMinimum === "boolean" && legacy.exclusiveMinimum === true) {
|
|
522
|
+
if (schema.minimum !== void 0) {
|
|
523
|
+
schema.exclusiveMinimum = schema.minimum;
|
|
524
|
+
delete schema.minimum;
|
|
525
|
+
} else {
|
|
526
|
+
delete legacy.exclusiveMinimum;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (legacy.exclusiveMaximum === false) {
|
|
530
|
+
delete legacy.exclusiveMaximum;
|
|
531
|
+
}
|
|
532
|
+
if (legacy.exclusiveMinimum === false) {
|
|
533
|
+
delete legacy.exclusiveMinimum;
|
|
534
|
+
}
|
|
535
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 1 && schema.const === void 0) {
|
|
536
|
+
schema.const = schema.enum[0];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function normalizeDraft07(schema) {
|
|
540
|
+
const legacy = schema;
|
|
541
|
+
if (Array.isArray(legacy.items)) {
|
|
542
|
+
schema.prefixItems = legacy.items;
|
|
543
|
+
if ("additionalItems" in legacy) {
|
|
544
|
+
if (typeof legacy.additionalItems === "object" || typeof legacy.additionalItems === "boolean") {
|
|
545
|
+
schema.items = legacy.additionalItems;
|
|
546
|
+
}
|
|
547
|
+
delete legacy.additionalItems;
|
|
548
|
+
} else {
|
|
549
|
+
delete legacy.items;
|
|
550
|
+
}
|
|
551
|
+
} else if ("additionalItems" in legacy) {
|
|
552
|
+
delete legacy.additionalItems;
|
|
553
|
+
}
|
|
554
|
+
if ("dependencies" in legacy) {
|
|
555
|
+
const deps = legacy.dependencies;
|
|
556
|
+
for (const [prop, value] of Object.entries(deps)) {
|
|
557
|
+
if (Array.isArray(value)) {
|
|
558
|
+
if (!schema.dependentRequired) {
|
|
559
|
+
schema.dependentRequired = {};
|
|
560
|
+
}
|
|
561
|
+
schema.dependentRequired[prop] = value;
|
|
562
|
+
} else {
|
|
563
|
+
if (!schema.dependentSchemas) {
|
|
564
|
+
schema.dependentSchemas = {};
|
|
565
|
+
}
|
|
566
|
+
schema.dependentSchemas[prop] = value;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
delete legacy.dependencies;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function normalizeGeneral(schema) {
|
|
573
|
+
const legacy = schema;
|
|
574
|
+
if (legacy.nullable === true) {
|
|
575
|
+
if (typeof schema.type === "string") {
|
|
576
|
+
schema.type = [schema.type, "null"];
|
|
577
|
+
} else if (Array.isArray(schema.type)) {
|
|
578
|
+
if (!schema.type.includes("null")) {
|
|
579
|
+
schema.type = [...schema.type, "null"];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
delete legacy.nullable;
|
|
583
|
+
}
|
|
584
|
+
if ("example" in legacy) {
|
|
585
|
+
if (!("examples" in schema)) {
|
|
586
|
+
schema.examples = [legacy.example];
|
|
587
|
+
}
|
|
588
|
+
delete legacy.example;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function normalizeDraft201909(schema) {
|
|
592
|
+
if ("$recursiveRef" in schema) {
|
|
593
|
+
let ref = schema.$recursiveRef;
|
|
594
|
+
if (ref === "#") {
|
|
595
|
+
ref = "#recursiveAnchor";
|
|
596
|
+
}
|
|
597
|
+
schema.$dynamicRef = ref;
|
|
598
|
+
delete schema.$recursiveRef;
|
|
599
|
+
}
|
|
600
|
+
if ("$recursiveAnchor" in schema) {
|
|
601
|
+
if (schema.$recursiveAnchor === true) {
|
|
602
|
+
schema.$dynamicAnchor = schema.$dynamicAnchor ?? "recursiveAnchor";
|
|
603
|
+
}
|
|
604
|
+
delete schema.$recursiveAnchor;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function normalizeNestedSchemas(schema, options) {
|
|
608
|
+
if (schema.$defs) {
|
|
609
|
+
schema.$defs = Object.fromEntries(
|
|
610
|
+
Object.entries(schema.$defs).map(([key, subSchema]) => [
|
|
611
|
+
key,
|
|
612
|
+
normalizeSchema(subSchema, options)
|
|
613
|
+
])
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
if ("definitions" in schema) {
|
|
617
|
+
const defs = schema.definitions;
|
|
618
|
+
if (!schema.$defs) {
|
|
619
|
+
schema.$defs = {};
|
|
620
|
+
}
|
|
621
|
+
for (const [key, subSchema] of Object.entries(defs)) {
|
|
622
|
+
schema.$defs[key] = normalizeSchema(subSchema, options);
|
|
623
|
+
}
|
|
624
|
+
delete schema.definitions;
|
|
625
|
+
}
|
|
626
|
+
if (schema.properties) {
|
|
627
|
+
schema.properties = Object.fromEntries(
|
|
628
|
+
Object.entries(schema.properties).map(([key, subSchema]) => [
|
|
629
|
+
key,
|
|
630
|
+
normalizeSchema(subSchema, options)
|
|
631
|
+
])
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (schema.patternProperties) {
|
|
635
|
+
schema.patternProperties = Object.fromEntries(
|
|
636
|
+
Object.entries(schema.patternProperties).map(([key, subSchema]) => [
|
|
637
|
+
key,
|
|
638
|
+
normalizeSchema(subSchema, options)
|
|
639
|
+
])
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
643
|
+
schema.additionalProperties = normalizeSchema(
|
|
644
|
+
schema.additionalProperties,
|
|
645
|
+
options
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
649
|
+
schema.items = normalizeSchema(schema.items, options);
|
|
650
|
+
}
|
|
651
|
+
if (schema.prefixItems) {
|
|
652
|
+
schema.prefixItems = schema.prefixItems.map(
|
|
653
|
+
(s) => normalizeSchema(s, options)
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (schema.contains) {
|
|
657
|
+
schema.contains = normalizeSchema(schema.contains, options);
|
|
658
|
+
}
|
|
659
|
+
if (schema.allOf) {
|
|
660
|
+
schema.allOf = schema.allOf.map((s) => normalizeSchema(s, options));
|
|
661
|
+
}
|
|
662
|
+
if (schema.anyOf) {
|
|
663
|
+
schema.anyOf = schema.anyOf.map((s) => normalizeSchema(s, options));
|
|
664
|
+
}
|
|
665
|
+
if (schema.oneOf) {
|
|
666
|
+
schema.oneOf = schema.oneOf.map((s) => normalizeSchema(s, options));
|
|
667
|
+
}
|
|
668
|
+
if (schema.not) {
|
|
669
|
+
schema.not = normalizeSchema(schema.not, options);
|
|
670
|
+
}
|
|
671
|
+
if (schema.if) {
|
|
672
|
+
schema.if = normalizeSchema(schema.if, options);
|
|
673
|
+
}
|
|
674
|
+
if (schema.then) {
|
|
675
|
+
schema.then = normalizeSchema(schema.then, options);
|
|
676
|
+
}
|
|
677
|
+
if (schema.else) {
|
|
678
|
+
schema.else = normalizeSchema(schema.else, options);
|
|
679
|
+
}
|
|
680
|
+
if (schema.dependentSchemas) {
|
|
681
|
+
schema.dependentSchemas = Object.fromEntries(
|
|
682
|
+
Object.entries(schema.dependentSchemas).map(([key, subSchema]) => [
|
|
683
|
+
key,
|
|
684
|
+
normalizeSchema(subSchema, options)
|
|
685
|
+
])
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (schema.unevaluatedItems) {
|
|
689
|
+
schema.unevaluatedItems = normalizeSchema(schema.unevaluatedItems, options);
|
|
690
|
+
}
|
|
691
|
+
if (schema.unevaluatedProperties) {
|
|
692
|
+
schema.unevaluatedProperties = normalizeSchema(
|
|
693
|
+
schema.unevaluatedProperties,
|
|
694
|
+
options
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
if (schema.propertyNames) {
|
|
698
|
+
schema.propertyNames = normalizeSchema(schema.propertyNames, options);
|
|
699
|
+
}
|
|
700
|
+
if (schema.contentSchema) {
|
|
701
|
+
schema.contentSchema = normalizeSchema(schema.contentSchema, options);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/validate.ts
|
|
706
|
+
var Validator = class {
|
|
707
|
+
formatValidator;
|
|
708
|
+
errorFormatter;
|
|
709
|
+
constructor(config = {}) {
|
|
710
|
+
this.formatValidator = config.formatValidator ?? stringFormatValidator;
|
|
711
|
+
this.errorFormatter = config.errorFormatter ?? defaultErrorFormatter;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Format an ErrorMessage to a localized string.
|
|
715
|
+
*/
|
|
716
|
+
formatError(msg) {
|
|
717
|
+
return this.errorFormatter(msg);
|
|
718
|
+
}
|
|
719
|
+
validate(schema, value, keywordLocation = "#", instanceLocation = "", options = {}) {
|
|
720
|
+
const { fastFail = false } = options;
|
|
721
|
+
const output = {
|
|
722
|
+
valid: false,
|
|
723
|
+
keywordLocation,
|
|
724
|
+
absoluteKeywordLocation: keywordLocation,
|
|
725
|
+
instanceLocation,
|
|
726
|
+
absoluteInstanceLocation: instanceLocation,
|
|
727
|
+
errors: []
|
|
728
|
+
};
|
|
729
|
+
if (schema.if) {
|
|
730
|
+
const ifResult = this.validate(
|
|
731
|
+
schema.if,
|
|
732
|
+
value,
|
|
733
|
+
keywordLocation + `/if`,
|
|
734
|
+
`${instanceLocation}`,
|
|
735
|
+
options
|
|
736
|
+
);
|
|
737
|
+
if (ifResult.valid) {
|
|
738
|
+
if (schema.then) {
|
|
739
|
+
const thenResult = this.validate(
|
|
740
|
+
schema.then,
|
|
741
|
+
value,
|
|
742
|
+
keywordLocation + `/then`,
|
|
743
|
+
instanceLocation,
|
|
744
|
+
options
|
|
745
|
+
);
|
|
746
|
+
if (!thenResult.valid) {
|
|
747
|
+
output.errors.push(...thenResult.errors || []);
|
|
748
|
+
if (fastFail) return output;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} else if (schema.else) {
|
|
752
|
+
const elseResult = this.validate(
|
|
753
|
+
schema.else,
|
|
754
|
+
value,
|
|
755
|
+
keywordLocation + `/else`,
|
|
756
|
+
instanceLocation,
|
|
757
|
+
options
|
|
758
|
+
);
|
|
759
|
+
if (!elseResult.valid) {
|
|
760
|
+
output.errors.push(...elseResult.errors || []);
|
|
761
|
+
if (fastFail) return output;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (schema.allOf) {
|
|
766
|
+
for (let index = 0; index < schema.allOf.length; index++) {
|
|
767
|
+
const subSchema = schema.allOf[index];
|
|
768
|
+
const result = this.validate(
|
|
769
|
+
subSchema,
|
|
770
|
+
value,
|
|
771
|
+
keywordLocation + `/allOf/${index}`,
|
|
772
|
+
instanceLocation,
|
|
773
|
+
options
|
|
774
|
+
);
|
|
775
|
+
if (!result.valid) {
|
|
776
|
+
output.errors.push(...result.errors || []);
|
|
777
|
+
if (fastFail) return output;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (schema.anyOf) {
|
|
782
|
+
let hasValid = false;
|
|
783
|
+
const errors = [];
|
|
784
|
+
for (let index = 0; index < schema.anyOf.length; index++) {
|
|
785
|
+
const subSchema = schema.anyOf[index];
|
|
786
|
+
const result = this.validate(
|
|
787
|
+
subSchema,
|
|
788
|
+
value,
|
|
789
|
+
keywordLocation + `/anyOf/${index}`,
|
|
790
|
+
instanceLocation,
|
|
791
|
+
options
|
|
792
|
+
);
|
|
793
|
+
if (result.valid) {
|
|
794
|
+
hasValid = true;
|
|
795
|
+
break;
|
|
796
|
+
} else {
|
|
797
|
+
errors.push(...result.errors || []);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (!hasValid) {
|
|
801
|
+
output.errors.push({
|
|
802
|
+
valid: false,
|
|
803
|
+
keywordLocation: `${keywordLocation}/anyOf`,
|
|
804
|
+
instanceLocation,
|
|
805
|
+
errors,
|
|
806
|
+
error: this.formatError({ key: "validation.anyOf" })
|
|
807
|
+
});
|
|
808
|
+
if (fastFail) return output;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (schema.oneOf) {
|
|
812
|
+
const results = schema.oneOf.map(
|
|
813
|
+
(subSchema, index) => this.validate(
|
|
814
|
+
subSchema,
|
|
815
|
+
value,
|
|
816
|
+
keywordLocation + `/oneOf/${index}`,
|
|
817
|
+
instanceLocation,
|
|
818
|
+
options
|
|
819
|
+
)
|
|
820
|
+
), validCount = results.filter((r) => r.valid).length;
|
|
821
|
+
if (validCount !== 1) {
|
|
822
|
+
output.errors.push({
|
|
823
|
+
valid: false,
|
|
824
|
+
keywordLocation: `${keywordLocation}/oneOf`,
|
|
825
|
+
instanceLocation,
|
|
826
|
+
errors: [],
|
|
827
|
+
annotations: results,
|
|
828
|
+
error: this.formatError({
|
|
829
|
+
key: "validation.oneOf",
|
|
830
|
+
params: { count: validCount }
|
|
831
|
+
})
|
|
832
|
+
});
|
|
833
|
+
if (fastFail) return output;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (schema.not) {
|
|
837
|
+
const result = this.validate(
|
|
838
|
+
schema.not,
|
|
839
|
+
value,
|
|
840
|
+
keywordLocation + `/not`,
|
|
841
|
+
instanceLocation,
|
|
842
|
+
options
|
|
843
|
+
);
|
|
844
|
+
if (result.valid) {
|
|
845
|
+
output.errors.push({
|
|
846
|
+
valid: false,
|
|
847
|
+
keywordLocation: `${keywordLocation}/not`,
|
|
848
|
+
instanceLocation,
|
|
849
|
+
errors: [],
|
|
850
|
+
annotations: [result],
|
|
851
|
+
error: this.formatError({ key: "validation.not" })
|
|
852
|
+
});
|
|
853
|
+
if (fastFail) return output;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (schema.const !== void 0) {
|
|
857
|
+
if (!deepEqual(value, schema.const)) {
|
|
858
|
+
output.errors.push({
|
|
859
|
+
valid: false,
|
|
860
|
+
keywordLocation: `${keywordLocation}/const`,
|
|
861
|
+
instanceLocation,
|
|
862
|
+
errors: [],
|
|
863
|
+
error: this.formatError({
|
|
864
|
+
key: "validation.const",
|
|
865
|
+
params: { value: schema.const }
|
|
866
|
+
})
|
|
867
|
+
});
|
|
868
|
+
if (fastFail) return output;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (schema.enum) {
|
|
872
|
+
if (!schema.enum.some((v) => deepEqual(value, v))) {
|
|
873
|
+
output.errors.push({
|
|
874
|
+
valid: false,
|
|
875
|
+
keywordLocation: `${keywordLocation}/enum`,
|
|
876
|
+
instanceLocation,
|
|
877
|
+
errors: [],
|
|
878
|
+
error: this.formatError({ key: "validation.enum" })
|
|
879
|
+
});
|
|
880
|
+
if (fastFail) return output;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
let instanceType = "";
|
|
884
|
+
if (schema.type) {
|
|
885
|
+
const allowedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
886
|
+
for (const type of allowedTypes) {
|
|
887
|
+
if (this.checkType(value, type)) {
|
|
888
|
+
instanceType = type;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (!instanceType) {
|
|
893
|
+
output.errors.push({
|
|
894
|
+
valid: false,
|
|
895
|
+
keywordLocation: `${keywordLocation}/type`,
|
|
896
|
+
instanceLocation,
|
|
897
|
+
errors: [],
|
|
898
|
+
error: this.formatError({
|
|
899
|
+
key: "validation.type",
|
|
900
|
+
params: { expected: allowedTypes.join(" or ") }
|
|
901
|
+
})
|
|
902
|
+
});
|
|
903
|
+
if (fastFail) return output;
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
instanceType = this.detectType(value);
|
|
907
|
+
}
|
|
908
|
+
if (instanceType === "object") {
|
|
909
|
+
this.validateObject(
|
|
910
|
+
schema,
|
|
911
|
+
value,
|
|
912
|
+
instanceLocation,
|
|
913
|
+
keywordLocation,
|
|
914
|
+
output,
|
|
915
|
+
options
|
|
916
|
+
);
|
|
917
|
+
} else if (instanceType === "array") {
|
|
918
|
+
this.validateArray(
|
|
919
|
+
schema,
|
|
920
|
+
value,
|
|
921
|
+
keywordLocation,
|
|
922
|
+
instanceLocation,
|
|
923
|
+
output,
|
|
924
|
+
options
|
|
925
|
+
);
|
|
926
|
+
} else if (instanceType === "string") {
|
|
927
|
+
this.validateString(
|
|
928
|
+
schema,
|
|
929
|
+
value,
|
|
930
|
+
keywordLocation,
|
|
931
|
+
instanceLocation,
|
|
932
|
+
output,
|
|
933
|
+
options
|
|
934
|
+
);
|
|
935
|
+
} else if (instanceType === "number") {
|
|
936
|
+
this.validateNumber(
|
|
937
|
+
schema,
|
|
938
|
+
value,
|
|
939
|
+
keywordLocation,
|
|
940
|
+
instanceLocation,
|
|
941
|
+
output,
|
|
942
|
+
options
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
output.valid = output.errors.length == 0;
|
|
946
|
+
output.error = output.valid ? void 0 : this.formatError({ key: "validation.failed" });
|
|
947
|
+
return output;
|
|
948
|
+
}
|
|
949
|
+
validateNumber(schema, value, keywordLocation, instanceLocation, output, options) {
|
|
950
|
+
const { fastFail = false } = options;
|
|
951
|
+
const addError = (keyword, msg) => {
|
|
952
|
+
output.errors.push({
|
|
953
|
+
valid: false,
|
|
954
|
+
keywordLocation: `${keywordLocation}/${keyword}`,
|
|
955
|
+
instanceLocation,
|
|
956
|
+
errors: [],
|
|
957
|
+
error: this.formatError(msg)
|
|
958
|
+
});
|
|
959
|
+
};
|
|
960
|
+
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
961
|
+
addError("maximum", {
|
|
962
|
+
key: "validation.maximum",
|
|
963
|
+
params: { value: schema.maximum }
|
|
964
|
+
});
|
|
965
|
+
if (fastFail) return;
|
|
966
|
+
}
|
|
967
|
+
if (schema.exclusiveMaximum !== void 0 && value >= schema.exclusiveMaximum) {
|
|
968
|
+
addError("exclusiveMaximum", {
|
|
969
|
+
key: "validation.exclusiveMaximum",
|
|
970
|
+
params: { value: schema.exclusiveMaximum }
|
|
971
|
+
});
|
|
972
|
+
if (fastFail) return;
|
|
973
|
+
}
|
|
974
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
975
|
+
addError("minimum", {
|
|
976
|
+
key: "validation.minimum",
|
|
977
|
+
params: { value: schema.minimum }
|
|
978
|
+
});
|
|
979
|
+
if (fastFail) return;
|
|
980
|
+
}
|
|
981
|
+
if (schema.exclusiveMinimum !== void 0 && value <= schema.exclusiveMinimum) {
|
|
982
|
+
addError("exclusiveMinimum", {
|
|
983
|
+
key: "validation.exclusiveMinimum",
|
|
984
|
+
params: { value: schema.exclusiveMinimum }
|
|
985
|
+
});
|
|
986
|
+
if (fastFail) return;
|
|
987
|
+
}
|
|
988
|
+
if (schema.multipleOf !== void 0) {
|
|
989
|
+
const remainder = value % schema.multipleOf;
|
|
990
|
+
if (Math.abs(remainder) > 1e-5 && Math.abs(remainder - schema.multipleOf) > 1e-5) {
|
|
991
|
+
addError("multipleOf", {
|
|
992
|
+
key: "validation.multipleOf",
|
|
993
|
+
params: { value: schema.multipleOf }
|
|
994
|
+
});
|
|
995
|
+
if (fastFail) return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
validateString(schema, value, keywordLocation, instanceLocation, output, options) {
|
|
1000
|
+
const { fastFail = false } = options;
|
|
1001
|
+
const addError = (keyword, msg) => {
|
|
1002
|
+
output.errors.push({
|
|
1003
|
+
valid: false,
|
|
1004
|
+
keywordLocation: `${keywordLocation}/${keyword}`,
|
|
1005
|
+
instanceLocation,
|
|
1006
|
+
errors: [],
|
|
1007
|
+
error: this.formatError(msg)
|
|
1008
|
+
});
|
|
1009
|
+
}, { length } = [...value];
|
|
1010
|
+
if (schema.maxLength !== void 0 && length > schema.maxLength) {
|
|
1011
|
+
addError("maxLength", {
|
|
1012
|
+
key: "validation.maxLength",
|
|
1013
|
+
params: { value: schema.maxLength }
|
|
1014
|
+
});
|
|
1015
|
+
if (fastFail) return;
|
|
1016
|
+
}
|
|
1017
|
+
if (schema.minLength !== void 0 && length < schema.minLength) {
|
|
1018
|
+
addError("minLength", {
|
|
1019
|
+
key: "validation.minLength",
|
|
1020
|
+
params: { value: schema.minLength }
|
|
1021
|
+
});
|
|
1022
|
+
if (fastFail) return;
|
|
1023
|
+
}
|
|
1024
|
+
if (schema.pattern !== void 0) {
|
|
1025
|
+
const regex = new RegExp(schema.pattern);
|
|
1026
|
+
if (!regex.test(value)) {
|
|
1027
|
+
addError("pattern", {
|
|
1028
|
+
key: "validation.pattern",
|
|
1029
|
+
params: { pattern: schema.pattern }
|
|
1030
|
+
});
|
|
1031
|
+
if (fastFail) return;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (schema.format !== void 0) {
|
|
1035
|
+
if (!this.validateFormat(schema.format, value)) {
|
|
1036
|
+
addError("format", {
|
|
1037
|
+
key: "validation.format",
|
|
1038
|
+
params: { format: schema.format }
|
|
1039
|
+
});
|
|
1040
|
+
if (fastFail) return;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
validateArray(schema, value, keywordLocation, instanceLocation, output, options) {
|
|
1045
|
+
const { fastFail = false, shallow = false } = options;
|
|
1046
|
+
const addError = (keyword, msg) => {
|
|
1047
|
+
output.errors.push({
|
|
1048
|
+
valid: false,
|
|
1049
|
+
keywordLocation: `${keywordLocation}/${keyword}`,
|
|
1050
|
+
instanceLocation,
|
|
1051
|
+
errors: [],
|
|
1052
|
+
error: this.formatError(msg)
|
|
1053
|
+
});
|
|
1054
|
+
};
|
|
1055
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
1056
|
+
addError("maxItems", {
|
|
1057
|
+
key: "validation.maxItems",
|
|
1058
|
+
params: { value: schema.maxItems }
|
|
1059
|
+
});
|
|
1060
|
+
if (fastFail) return;
|
|
1061
|
+
}
|
|
1062
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
1063
|
+
addError("minItems", {
|
|
1064
|
+
key: "validation.minItems",
|
|
1065
|
+
params: { value: schema.minItems }
|
|
1066
|
+
});
|
|
1067
|
+
if (fastFail) return;
|
|
1068
|
+
}
|
|
1069
|
+
if (schema.uniqueItems) {
|
|
1070
|
+
for (let i = 0; i < value.length; i++) {
|
|
1071
|
+
for (let j = i + 1; j < value.length; j++) {
|
|
1072
|
+
if (deepEqual(value[i], value[j])) {
|
|
1073
|
+
addError("uniqueItems", { key: "validation.uniqueItems" });
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
let prefixItemsLength = 0;
|
|
1080
|
+
if (schema.prefixItems) {
|
|
1081
|
+
prefixItemsLength = schema.prefixItems.length;
|
|
1082
|
+
for (let index = 0; index < schema.prefixItems.length; index++) {
|
|
1083
|
+
const itemSchema = schema.prefixItems[index];
|
|
1084
|
+
if (index < value.length) {
|
|
1085
|
+
if (!shallow) {
|
|
1086
|
+
const result = this.validate(
|
|
1087
|
+
itemSchema,
|
|
1088
|
+
value[index],
|
|
1089
|
+
`${keywordLocation}/prefixItems/${index}`,
|
|
1090
|
+
`${instanceLocation}/${index}`,
|
|
1091
|
+
options
|
|
1092
|
+
);
|
|
1093
|
+
if (!result.valid) {
|
|
1094
|
+
output.errors.push(result);
|
|
1095
|
+
if (fastFail) return;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (schema.items && value.length > prefixItemsLength) {
|
|
1102
|
+
for (let i = prefixItemsLength; i < value.length; i++) {
|
|
1103
|
+
if (!shallow) {
|
|
1104
|
+
const result = this.validate(
|
|
1105
|
+
schema.items,
|
|
1106
|
+
value[i],
|
|
1107
|
+
`${keywordLocation}/items`,
|
|
1108
|
+
`${instanceLocation}/${i}`,
|
|
1109
|
+
options
|
|
1110
|
+
);
|
|
1111
|
+
if (!result.valid) {
|
|
1112
|
+
output.errors.push(result);
|
|
1113
|
+
if (fastFail) return;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (schema.contains) {
|
|
1119
|
+
let containsCount = 0;
|
|
1120
|
+
for (let i = 0; i < value.length; i++) {
|
|
1121
|
+
const result = this.validate(
|
|
1122
|
+
schema.contains,
|
|
1123
|
+
value[i],
|
|
1124
|
+
`${keywordLocation}/contains`,
|
|
1125
|
+
`${instanceLocation}/${i}`,
|
|
1126
|
+
options
|
|
1127
|
+
);
|
|
1128
|
+
if (result.valid) {
|
|
1129
|
+
containsCount++;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (schema.minContains !== void 0) {
|
|
1133
|
+
if (containsCount < schema.minContains) {
|
|
1134
|
+
addError("minContains", {
|
|
1135
|
+
key: "validation.minContains",
|
|
1136
|
+
params: { value: schema.minContains }
|
|
1137
|
+
});
|
|
1138
|
+
if (fastFail) return;
|
|
1139
|
+
}
|
|
1140
|
+
} else if (schema.minContains === void 0 && containsCount === 0) {
|
|
1141
|
+
addError("contains", { key: "validation.contains" });
|
|
1142
|
+
if (fastFail) return;
|
|
1143
|
+
}
|
|
1144
|
+
if (schema.maxContains !== void 0) {
|
|
1145
|
+
if (containsCount > schema.maxContains) {
|
|
1146
|
+
addError("maxContains", {
|
|
1147
|
+
key: "validation.maxContains",
|
|
1148
|
+
params: { value: schema.maxContains }
|
|
1149
|
+
});
|
|
1150
|
+
if (fastFail) return;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
validateObject(schema, value, keywordLocation, instanceLocation, output, options) {
|
|
1156
|
+
const { fastFail = false, shallow = false } = options;
|
|
1157
|
+
const addError = (keyword, msg) => {
|
|
1158
|
+
output.errors.push({
|
|
1159
|
+
valid: false,
|
|
1160
|
+
keywordLocation: `${keywordLocation}/${keyword}`,
|
|
1161
|
+
instanceLocation,
|
|
1162
|
+
errors: [],
|
|
1163
|
+
error: this.formatError(msg)
|
|
1164
|
+
});
|
|
1165
|
+
}, keys = Object.keys(value);
|
|
1166
|
+
if (schema.maxProperties !== void 0 && keys.length > schema.maxProperties) {
|
|
1167
|
+
addError("maxProperties", {
|
|
1168
|
+
key: "validation.maxProperties",
|
|
1169
|
+
params: { value: schema.maxProperties }
|
|
1170
|
+
});
|
|
1171
|
+
if (fastFail) return;
|
|
1172
|
+
}
|
|
1173
|
+
if (schema.minProperties !== void 0 && keys.length < schema.minProperties) {
|
|
1174
|
+
addError("minProperties", {
|
|
1175
|
+
key: "validation.minProperties",
|
|
1176
|
+
params: { value: schema.minProperties }
|
|
1177
|
+
});
|
|
1178
|
+
if (fastFail) return;
|
|
1179
|
+
}
|
|
1180
|
+
if (schema.required) {
|
|
1181
|
+
for (const req of schema.required) {
|
|
1182
|
+
if (!(req in value)) {
|
|
1183
|
+
addError("required", {
|
|
1184
|
+
key: "validation.required",
|
|
1185
|
+
params: { property: req }
|
|
1186
|
+
});
|
|
1187
|
+
if (fastFail) return;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (schema.dependentRequired) {
|
|
1192
|
+
for (const [prop, requiredProps] of Object.entries(
|
|
1193
|
+
schema.dependentRequired
|
|
1194
|
+
)) {
|
|
1195
|
+
if (prop in value) {
|
|
1196
|
+
for (const req of requiredProps) {
|
|
1197
|
+
if (!(req in value)) {
|
|
1198
|
+
addError("dependentRequired", {
|
|
1199
|
+
key: "validation.dependentRequired",
|
|
1200
|
+
params: { source: prop, target: req }
|
|
1201
|
+
});
|
|
1202
|
+
if (fastFail) return;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
const validatedKeys = /* @__PURE__ */ new Set();
|
|
1209
|
+
if (schema.properties) {
|
|
1210
|
+
for (const [prop, propSchema] of Object.entries(schema.properties)) {
|
|
1211
|
+
if (prop in value) {
|
|
1212
|
+
validatedKeys.add(prop);
|
|
1213
|
+
if (!shallow) {
|
|
1214
|
+
const result = this.validate(
|
|
1215
|
+
propSchema,
|
|
1216
|
+
value[prop],
|
|
1217
|
+
`${keywordLocation}/properties/${prop}`,
|
|
1218
|
+
`${instanceLocation}/${prop}`,
|
|
1219
|
+
options
|
|
1220
|
+
);
|
|
1221
|
+
if (!result.valid) {
|
|
1222
|
+
output.errors.push(result);
|
|
1223
|
+
if (fastFail) return;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (schema.patternProperties) {
|
|
1230
|
+
for (const [pattern, propSchema] of Object.entries(
|
|
1231
|
+
schema.patternProperties
|
|
1232
|
+
)) {
|
|
1233
|
+
const regex = new RegExp(pattern);
|
|
1234
|
+
for (const key of keys) {
|
|
1235
|
+
if (regex.test(key)) {
|
|
1236
|
+
validatedKeys.add(key);
|
|
1237
|
+
if (!shallow) {
|
|
1238
|
+
const result = this.validate(
|
|
1239
|
+
propSchema,
|
|
1240
|
+
value[key],
|
|
1241
|
+
`${keywordLocation}/patternProperties/${jsonPointerEscape(pattern)}`,
|
|
1242
|
+
`${instanceLocation}/${jsonPointerEscape(key)}`,
|
|
1243
|
+
options
|
|
1244
|
+
);
|
|
1245
|
+
if (!result.valid) {
|
|
1246
|
+
output.errors.push(...result.errors || []);
|
|
1247
|
+
if (fastFail) return;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (schema.additionalProperties !== void 0) {
|
|
1255
|
+
const additionalKeys = keys.filter((k) => !validatedKeys.has(k));
|
|
1256
|
+
if (typeof schema.additionalProperties === "boolean") {
|
|
1257
|
+
if (!schema.additionalProperties && additionalKeys.length > 0) {
|
|
1258
|
+
addError("additionalProperties", {
|
|
1259
|
+
key: "validation.additionalProperties",
|
|
1260
|
+
params: { properties: additionalKeys.join(", ") }
|
|
1261
|
+
});
|
|
1262
|
+
if (fastFail) return;
|
|
1263
|
+
}
|
|
1264
|
+
} else {
|
|
1265
|
+
for (const key of additionalKeys) {
|
|
1266
|
+
if (!shallow) {
|
|
1267
|
+
const result = this.validate(
|
|
1268
|
+
schema.additionalProperties,
|
|
1269
|
+
value[key],
|
|
1270
|
+
`${keywordLocation}/additionalProperties`,
|
|
1271
|
+
`${instanceLocation}/${jsonPointerEscape(key)}`,
|
|
1272
|
+
options
|
|
1273
|
+
);
|
|
1274
|
+
if (!result.valid) {
|
|
1275
|
+
output.errors.push(result);
|
|
1276
|
+
if (fastFail) return;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if (schema.propertyNames) {
|
|
1283
|
+
for (const key of keys) {
|
|
1284
|
+
const result = this.validate(
|
|
1285
|
+
schema.propertyNames,
|
|
1286
|
+
key,
|
|
1287
|
+
`${keywordLocation}/propertyNames`,
|
|
1288
|
+
`${instanceLocation}/${jsonPointerEscape(key)}`,
|
|
1289
|
+
options
|
|
1290
|
+
);
|
|
1291
|
+
if (!result.valid) {
|
|
1292
|
+
output.errors.push(result);
|
|
1293
|
+
if (fastFail) return;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (schema.dependentSchemas) {
|
|
1298
|
+
for (const [prop, depSchema] of Object.entries(schema.dependentSchemas)) {
|
|
1299
|
+
if (prop in value) {
|
|
1300
|
+
const result = this.validate(
|
|
1301
|
+
depSchema,
|
|
1302
|
+
value,
|
|
1303
|
+
`${keywordLocation}/dependentSchemas/${jsonPointerEscape(prop)}`,
|
|
1304
|
+
instanceLocation,
|
|
1305
|
+
options
|
|
1306
|
+
);
|
|
1307
|
+
if (!result.valid) {
|
|
1308
|
+
output.errors.push(result);
|
|
1309
|
+
if (fastFail) return;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
detectType(value) {
|
|
1316
|
+
if (value === null || value === void 0) return "null";
|
|
1317
|
+
if (Array.isArray(value)) return "array";
|
|
1318
|
+
if (Number.isInteger(value)) return "integer";
|
|
1319
|
+
return typeof value;
|
|
1320
|
+
}
|
|
1321
|
+
checkType(value, type) {
|
|
1322
|
+
return matchSchemaType(value, type);
|
|
1323
|
+
}
|
|
1324
|
+
validateFormat(format, value) {
|
|
1325
|
+
return this.formatValidator.validate(format, value);
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
function validateSchema(schema, value, instancePath = "", schemaPath = "#", fastFail = false) {
|
|
1329
|
+
const normalizedSchema = normalizeSchema(schema);
|
|
1330
|
+
const validator = new Validator();
|
|
1331
|
+
return validator.validate(normalizedSchema, value, schemaPath, instancePath, {
|
|
1332
|
+
fastFail
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/schema-util.ts
|
|
1337
|
+
var schemaUtilLogger = {
|
|
1338
|
+
warn: (message) => console.warn(message)
|
|
1339
|
+
};
|
|
1340
|
+
function resolveRef(ref, rootSchema, visited = /* @__PURE__ */ new Set()) {
|
|
1341
|
+
if (!ref.startsWith("#")) {
|
|
1342
|
+
schemaUtilLogger?.warn(`External $ref not supported: ${ref}`);
|
|
1343
|
+
return void 0;
|
|
1344
|
+
}
|
|
1345
|
+
if (visited.has(ref)) {
|
|
1346
|
+
schemaUtilLogger?.warn(`Circular $ref detected: ${ref}`);
|
|
1347
|
+
return void 0;
|
|
1348
|
+
}
|
|
1349
|
+
visited.add(ref);
|
|
1350
|
+
const pointer = ref.slice(1);
|
|
1351
|
+
if (pointer === "" || pointer === "/") {
|
|
1352
|
+
return rootSchema;
|
|
1353
|
+
}
|
|
1354
|
+
const segments = parseJsonPointer(pointer);
|
|
1355
|
+
let current = rootSchema;
|
|
1356
|
+
for (const segment of segments) {
|
|
1357
|
+
if (current === null || current === void 0) {
|
|
1358
|
+
return void 0;
|
|
1359
|
+
}
|
|
1360
|
+
if (typeof current !== "object") {
|
|
1361
|
+
return void 0;
|
|
1362
|
+
}
|
|
1363
|
+
current = current[segment];
|
|
1364
|
+
}
|
|
1365
|
+
if (current === null || current === void 0) {
|
|
1366
|
+
return void 0;
|
|
1367
|
+
}
|
|
1368
|
+
const resolved = current;
|
|
1369
|
+
if (resolved.$ref) {
|
|
1370
|
+
return resolveRef(resolved.$ref, rootSchema, visited);
|
|
1371
|
+
}
|
|
1372
|
+
return resolved;
|
|
1373
|
+
}
|
|
1374
|
+
function dereferenceSchema(schema, rootSchema) {
|
|
1375
|
+
if (!schema.$ref) {
|
|
1376
|
+
return schema;
|
|
1377
|
+
}
|
|
1378
|
+
const resolved = resolveRef(schema.$ref, rootSchema);
|
|
1379
|
+
if (!resolved) {
|
|
1380
|
+
const { $ref: _2, ...rest } = schema;
|
|
1381
|
+
return rest;
|
|
1382
|
+
}
|
|
1383
|
+
const { $ref: _, ...siblings } = schema;
|
|
1384
|
+
return mergeSchema(resolved, siblings);
|
|
1385
|
+
}
|
|
1386
|
+
function dereferenceSchemaDeep(schema, rootSchema, processed = /* @__PURE__ */ new WeakMap(), inProgress = /* @__PURE__ */ new WeakSet()) {
|
|
1387
|
+
const cached = processed.get(schema);
|
|
1388
|
+
if (cached !== void 0) {
|
|
1389
|
+
return cached;
|
|
1390
|
+
}
|
|
1391
|
+
if (inProgress.has(schema)) {
|
|
1392
|
+
return schema;
|
|
1393
|
+
}
|
|
1394
|
+
inProgress.add(schema);
|
|
1395
|
+
let result = dereferenceSchema(schema, rootSchema);
|
|
1396
|
+
if (result === schema) {
|
|
1397
|
+
result = { ...schema };
|
|
1398
|
+
}
|
|
1399
|
+
if (result.properties) {
|
|
1400
|
+
result.properties = Object.fromEntries(
|
|
1401
|
+
Object.entries(result.properties).map(([key, subSchema]) => [
|
|
1402
|
+
key,
|
|
1403
|
+
dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
|
|
1404
|
+
])
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
if (result.patternProperties) {
|
|
1408
|
+
result.patternProperties = Object.fromEntries(
|
|
1409
|
+
Object.entries(result.patternProperties).map(([key, subSchema]) => [
|
|
1410
|
+
key,
|
|
1411
|
+
dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
|
|
1412
|
+
])
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
if (result.additionalProperties && typeof result.additionalProperties === "object") {
|
|
1416
|
+
result.additionalProperties = dereferenceSchemaDeep(
|
|
1417
|
+
result.additionalProperties,
|
|
1418
|
+
rootSchema,
|
|
1419
|
+
processed,
|
|
1420
|
+
inProgress
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
if (result.items) {
|
|
1424
|
+
result.items = dereferenceSchemaDeep(
|
|
1425
|
+
result.items,
|
|
1426
|
+
rootSchema,
|
|
1427
|
+
processed,
|
|
1428
|
+
inProgress
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
if (result.prefixItems) {
|
|
1432
|
+
result.prefixItems = result.prefixItems.map(
|
|
1433
|
+
(s) => dereferenceSchemaDeep(s, rootSchema, processed, inProgress)
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
if (result.contains) {
|
|
1437
|
+
result.contains = dereferenceSchemaDeep(
|
|
1438
|
+
result.contains,
|
|
1439
|
+
rootSchema,
|
|
1440
|
+
processed,
|
|
1441
|
+
inProgress
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
1445
|
+
const subSchemas = result[keyword];
|
|
1446
|
+
if (subSchemas) {
|
|
1447
|
+
result[keyword] = subSchemas.map(
|
|
1448
|
+
(s) => dereferenceSchemaDeep(s, rootSchema, processed, inProgress)
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
if (result.not) {
|
|
1453
|
+
result.not = dereferenceSchemaDeep(
|
|
1454
|
+
result.not,
|
|
1455
|
+
rootSchema,
|
|
1456
|
+
processed,
|
|
1457
|
+
inProgress
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
if (result.if) {
|
|
1461
|
+
result.if = dereferenceSchemaDeep(
|
|
1462
|
+
result.if,
|
|
1463
|
+
rootSchema,
|
|
1464
|
+
processed,
|
|
1465
|
+
inProgress
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
if (result.then) {
|
|
1469
|
+
result.then = dereferenceSchemaDeep(
|
|
1470
|
+
result.then,
|
|
1471
|
+
rootSchema,
|
|
1472
|
+
processed,
|
|
1473
|
+
inProgress
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
if (result.else) {
|
|
1477
|
+
result.else = dereferenceSchemaDeep(
|
|
1478
|
+
result.else,
|
|
1479
|
+
rootSchema,
|
|
1480
|
+
processed,
|
|
1481
|
+
inProgress
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
if (result.dependentSchemas) {
|
|
1485
|
+
result.dependentSchemas = Object.fromEntries(
|
|
1486
|
+
Object.entries(result.dependentSchemas).map(([key, subSchema]) => [
|
|
1487
|
+
key,
|
|
1488
|
+
dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
|
|
1489
|
+
])
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
if (result.$defs) {
|
|
1493
|
+
result.$defs = Object.fromEntries(
|
|
1494
|
+
Object.entries(result.$defs).map(([key, subSchema]) => [
|
|
1495
|
+
key,
|
|
1496
|
+
dereferenceSchemaDeep(subSchema, rootSchema, processed, inProgress)
|
|
1497
|
+
])
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
processed.set(schema, result);
|
|
1501
|
+
return result;
|
|
1502
|
+
}
|
|
1503
|
+
var MAX_EXTRACT_DEPTH = 100;
|
|
1504
|
+
function extractReferencedPaths(conditionSchema, basePath = "", depth = 0) {
|
|
1505
|
+
if (depth > MAX_EXTRACT_DEPTH) {
|
|
1506
|
+
console.warn(
|
|
1507
|
+
`extractReferencedPaths: max depth (${MAX_EXTRACT_DEPTH}) exceeded at path: ${basePath}`
|
|
1508
|
+
);
|
|
1509
|
+
return [];
|
|
1510
|
+
}
|
|
1511
|
+
const paths = [];
|
|
1512
|
+
const schema = conditionSchema;
|
|
1513
|
+
if (schema.properties) {
|
|
1514
|
+
for (const key of Object.keys(schema.properties)) {
|
|
1515
|
+
const childPath = basePath ? `${basePath}/${key}` : `/${key}`;
|
|
1516
|
+
paths.push(childPath);
|
|
1517
|
+
paths.push(
|
|
1518
|
+
...extractReferencedPaths(schema.properties[key], childPath, depth + 1)
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (schema.items && typeof schema.items === "object") {
|
|
1523
|
+
paths.push(basePath || "/");
|
|
1524
|
+
paths.push(...extractReferencedPaths(schema.items, basePath, depth + 1));
|
|
1525
|
+
}
|
|
1526
|
+
if (schema.prefixItems) {
|
|
1527
|
+
schema.prefixItems.forEach((itemSchema, index) => {
|
|
1528
|
+
const indexPath = basePath ? `${basePath}/${index}` : `/${index}`;
|
|
1529
|
+
paths.push(indexPath);
|
|
1530
|
+
paths.push(...extractReferencedPaths(itemSchema, indexPath, depth + 1));
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
if (schema.const !== void 0 || schema.enum) {
|
|
1534
|
+
if (basePath) {
|
|
1535
|
+
paths.push(basePath);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (schema.type && basePath) {
|
|
1539
|
+
paths.push(basePath);
|
|
1540
|
+
}
|
|
1541
|
+
const valueConstraints = [
|
|
1542
|
+
"minimum",
|
|
1543
|
+
"maximum",
|
|
1544
|
+
"exclusiveMinimum",
|
|
1545
|
+
"exclusiveMaximum",
|
|
1546
|
+
"minLength",
|
|
1547
|
+
"maxLength",
|
|
1548
|
+
"pattern",
|
|
1549
|
+
"format",
|
|
1550
|
+
"minItems",
|
|
1551
|
+
"maxItems",
|
|
1552
|
+
"uniqueItems",
|
|
1553
|
+
"minProperties",
|
|
1554
|
+
"maxProperties"
|
|
1555
|
+
];
|
|
1556
|
+
for (const constraint of valueConstraints) {
|
|
1557
|
+
if (schema[constraint] !== void 0 && basePath) {
|
|
1558
|
+
paths.push(basePath);
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
if (schema.required) {
|
|
1563
|
+
for (const req of schema.required) {
|
|
1564
|
+
paths.push(basePath ? `${basePath}/${req}` : `/${req}`);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (schema.dependentRequired) {
|
|
1568
|
+
for (const [prop, reqs] of Object.entries(schema.dependentRequired)) {
|
|
1569
|
+
paths.push(basePath ? `${basePath}/${prop}` : `/${prop}`);
|
|
1570
|
+
for (const req of reqs) {
|
|
1571
|
+
paths.push(basePath ? `${basePath}/${req}` : `/${req}`);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (schema.dependentSchemas) {
|
|
1576
|
+
for (const [prop, subSchema] of Object.entries(schema.dependentSchemas)) {
|
|
1577
|
+
paths.push(basePath ? `${basePath}/${prop}` : `/${prop}`);
|
|
1578
|
+
paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
if (schema.if) {
|
|
1582
|
+
paths.push(...extractReferencedPaths(schema.if, basePath, depth + 1));
|
|
1583
|
+
}
|
|
1584
|
+
if (schema.then) {
|
|
1585
|
+
paths.push(...extractReferencedPaths(schema.then, basePath, depth + 1));
|
|
1586
|
+
}
|
|
1587
|
+
if (schema.else) {
|
|
1588
|
+
paths.push(...extractReferencedPaths(schema.else, basePath, depth + 1));
|
|
1589
|
+
}
|
|
1590
|
+
for (const keyword of ["allOf", "anyOf", "oneOf"]) {
|
|
1591
|
+
const subSchemas = schema[keyword];
|
|
1592
|
+
if (subSchemas) {
|
|
1593
|
+
for (const subSchema of subSchemas) {
|
|
1594
|
+
paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (schema.dependentSchemas) {
|
|
1599
|
+
for (const [key, subSchema] of Object.entries(schema.dependentSchemas)) {
|
|
1600
|
+
const keyPath = basePath ? `${basePath}/${key}` : `/${key}`;
|
|
1601
|
+
paths.push(keyPath);
|
|
1602
|
+
paths.push(...extractReferencedPaths(subSchema, basePath, depth + 1));
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
if (schema.contains) {
|
|
1606
|
+
paths.push(basePath || "/");
|
|
1607
|
+
paths.push(...extractReferencedPaths(schema.contains, basePath, depth + 1));
|
|
1608
|
+
}
|
|
1609
|
+
return [...new Set(paths)];
|
|
1610
|
+
}
|
|
1611
|
+
function resolveEffectiveSchema(validator, schema, value, keywordLocation, instanceLocation) {
|
|
1612
|
+
let effective = schema;
|
|
1613
|
+
if (effective.if) {
|
|
1614
|
+
const output = validator.validate(
|
|
1615
|
+
effective.if,
|
|
1616
|
+
value,
|
|
1617
|
+
`${keywordLocation}/if`,
|
|
1618
|
+
instanceLocation
|
|
1619
|
+
);
|
|
1620
|
+
if (output.valid) {
|
|
1621
|
+
if (effective.then) {
|
|
1622
|
+
const res = resolveEffectiveSchema(
|
|
1623
|
+
validator,
|
|
1624
|
+
effective.then,
|
|
1625
|
+
value,
|
|
1626
|
+
`${keywordLocation}/then`,
|
|
1627
|
+
instanceLocation
|
|
1628
|
+
);
|
|
1629
|
+
effective = mergeSchema(effective, res.effectiveSchema);
|
|
1630
|
+
}
|
|
1631
|
+
} else {
|
|
1632
|
+
if (effective.else) {
|
|
1633
|
+
const res = resolveEffectiveSchema(
|
|
1634
|
+
validator,
|
|
1635
|
+
effective.else,
|
|
1636
|
+
value,
|
|
1637
|
+
`${keywordLocation}/else`,
|
|
1638
|
+
instanceLocation
|
|
1639
|
+
);
|
|
1640
|
+
effective = mergeSchema(effective, res.effectiveSchema);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
const { if: _, then: __, else: ___, ...rest } = effective;
|
|
1644
|
+
effective = rest;
|
|
1645
|
+
}
|
|
1646
|
+
if (effective.allOf) {
|
|
1647
|
+
for (const [index, subschema] of effective.allOf.entries()) {
|
|
1648
|
+
const res = resolveEffectiveSchema(
|
|
1649
|
+
validator,
|
|
1650
|
+
subschema,
|
|
1651
|
+
value,
|
|
1652
|
+
`${keywordLocation}/allOf/${index}`,
|
|
1653
|
+
instanceLocation
|
|
1654
|
+
);
|
|
1655
|
+
effective = mergeSchema(effective, res.effectiveSchema);
|
|
1656
|
+
}
|
|
1657
|
+
const { allOf: _, ...rest } = effective;
|
|
1658
|
+
effective = rest;
|
|
1659
|
+
}
|
|
1660
|
+
if (effective.anyOf) {
|
|
1661
|
+
for (const [index, subschema] of effective.anyOf.entries()) {
|
|
1662
|
+
const output = validator.validate(
|
|
1663
|
+
subschema,
|
|
1664
|
+
value,
|
|
1665
|
+
keywordLocation + `/anyOf/` + index,
|
|
1666
|
+
instanceLocation
|
|
1667
|
+
);
|
|
1668
|
+
if (output.valid) {
|
|
1669
|
+
const res = resolveEffectiveSchema(
|
|
1670
|
+
validator,
|
|
1671
|
+
subschema,
|
|
1672
|
+
value,
|
|
1673
|
+
keywordLocation + `/anyOf/` + index,
|
|
1674
|
+
instanceLocation
|
|
1675
|
+
);
|
|
1676
|
+
effective = mergeSchema(effective, res.effectiveSchema);
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
const { anyOf: _, ...rest } = effective;
|
|
1681
|
+
effective = rest;
|
|
1682
|
+
}
|
|
1683
|
+
if (effective.oneOf) {
|
|
1684
|
+
let validCount = 0;
|
|
1685
|
+
let lastValidSchema = null;
|
|
1686
|
+
for (const [index, subschema] of effective.oneOf.entries()) {
|
|
1687
|
+
const output = validator.validate(
|
|
1688
|
+
subschema,
|
|
1689
|
+
value,
|
|
1690
|
+
`${keywordLocation}/oneOf/${index}`,
|
|
1691
|
+
instanceLocation
|
|
1692
|
+
);
|
|
1693
|
+
if (output.valid) {
|
|
1694
|
+
validCount++;
|
|
1695
|
+
lastValidSchema = subschema;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (validCount === 1 && lastValidSchema) {
|
|
1699
|
+
effective = mergeSchema(effective, lastValidSchema);
|
|
1700
|
+
}
|
|
1701
|
+
const { oneOf: _, ...rest } = effective;
|
|
1702
|
+
effective = rest;
|
|
1703
|
+
}
|
|
1704
|
+
let type = "unknown";
|
|
1705
|
+
if (effective.type) {
|
|
1706
|
+
const allowedTypes = Array.isArray(effective.type) ? effective.type : [effective.type];
|
|
1707
|
+
const matched = allowedTypes.find((t) => matchSchemaType(value, t));
|
|
1708
|
+
if (matched) {
|
|
1709
|
+
type = matched;
|
|
1710
|
+
} else {
|
|
1711
|
+
type = allowedTypes[0];
|
|
1712
|
+
}
|
|
1713
|
+
} else {
|
|
1714
|
+
type = detectSchemaType(value);
|
|
1715
|
+
}
|
|
1716
|
+
const validationOutput = validator.validate(
|
|
1717
|
+
effective,
|
|
1718
|
+
value,
|
|
1719
|
+
keywordLocation,
|
|
1720
|
+
instanceLocation,
|
|
1721
|
+
{ shallow: true }
|
|
1722
|
+
);
|
|
1723
|
+
return {
|
|
1724
|
+
effectiveSchema: effective,
|
|
1725
|
+
type,
|
|
1726
|
+
error: validationOutput.valid ? void 0 : validationOutput
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
function mergeStrings(a, b) {
|
|
1730
|
+
if (a === void 0) return b;
|
|
1731
|
+
if (b === void 0) return a;
|
|
1732
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...a, ...b]));
|
|
1733
|
+
return merged.length === 0 ? void 0 : merged;
|
|
1734
|
+
}
|
|
1735
|
+
function mergeType(a, b) {
|
|
1736
|
+
if (a === void 0) return b;
|
|
1737
|
+
if (b === void 0) return a;
|
|
1738
|
+
const arrayA = Array.isArray(a) ? a : [a];
|
|
1739
|
+
const arrayB = Array.isArray(b) ? b : [b];
|
|
1740
|
+
const merged = arrayA.filter((t) => arrayB.includes(t));
|
|
1741
|
+
if (merged.length === 0) return void 0;
|
|
1742
|
+
return merged.length === 1 ? merged[0] : merged;
|
|
1743
|
+
}
|
|
1744
|
+
function mergeSchemaArrays(a, b) {
|
|
1745
|
+
if (a === void 0) return b;
|
|
1746
|
+
if (b === void 0) return a;
|
|
1747
|
+
return [...a, ...b];
|
|
1748
|
+
}
|
|
1749
|
+
function mergeSchema(base, override) {
|
|
1750
|
+
if (!override) return base;
|
|
1751
|
+
const merged = {
|
|
1752
|
+
...base,
|
|
1753
|
+
...override
|
|
1754
|
+
};
|
|
1755
|
+
if (base.$defs || override.$defs) {
|
|
1756
|
+
merged.$defs = {
|
|
1757
|
+
...base.$defs,
|
|
1758
|
+
...override.$defs
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
const mergedRequired = mergeStrings(base.required, override.required);
|
|
1762
|
+
if (mergedRequired !== void 0) {
|
|
1763
|
+
merged.required = mergedRequired;
|
|
1764
|
+
}
|
|
1765
|
+
const mergedType = mergeType(base.type, override.type);
|
|
1766
|
+
if (mergedType !== void 0) {
|
|
1767
|
+
merged.type = mergedType;
|
|
1768
|
+
}
|
|
1769
|
+
if (base.dependentRequired || override.dependentRequired) {
|
|
1770
|
+
merged.dependentRequired = {
|
|
1771
|
+
...base.dependentRequired,
|
|
1772
|
+
...override.dependentRequired
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
if (base.properties || override.properties) {
|
|
1776
|
+
merged.properties = {
|
|
1777
|
+
...base.properties,
|
|
1778
|
+
...override.properties
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
if (base.patternProperties || override.patternProperties) {
|
|
1782
|
+
merged.patternProperties = {
|
|
1783
|
+
...base.patternProperties,
|
|
1784
|
+
...override.patternProperties
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
if (base.items && override.items) {
|
|
1788
|
+
merged.items = mergeSchema(base.items, override.items);
|
|
1789
|
+
} else if (base.items) {
|
|
1790
|
+
merged.items = base.items;
|
|
1791
|
+
} else if (override.items) {
|
|
1792
|
+
merged.items = override.items;
|
|
1793
|
+
}
|
|
1794
|
+
if (base.prefixItems || override.prefixItems) {
|
|
1795
|
+
merged.prefixItems = [];
|
|
1796
|
+
const len = Math.max(
|
|
1797
|
+
base.prefixItems?.length || 0,
|
|
1798
|
+
override.prefixItems?.length || 0
|
|
1799
|
+
);
|
|
1800
|
+
for (let i = 0; i < len; i++) {
|
|
1801
|
+
const baseSchema = base.prefixItems?.[i];
|
|
1802
|
+
const overrideSchema = override.prefixItems?.[i];
|
|
1803
|
+
if (baseSchema && overrideSchema) {
|
|
1804
|
+
merged.prefixItems.push(mergeSchema(baseSchema, overrideSchema));
|
|
1805
|
+
} else {
|
|
1806
|
+
const schema = baseSchema || overrideSchema;
|
|
1807
|
+
if (schema) {
|
|
1808
|
+
merged.prefixItems.push(schema);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
const combinatorKeywords = ["allOf", "anyOf", "oneOf"];
|
|
1814
|
+
for (const keyword of combinatorKeywords) {
|
|
1815
|
+
const mergedArray = mergeSchemaArrays(base[keyword], override[keyword]);
|
|
1816
|
+
if (mergedArray !== void 0) {
|
|
1817
|
+
merged[keyword] = mergedArray;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (base.dependentSchemas || override.dependentSchemas) {
|
|
1821
|
+
merged.dependentSchemas = {
|
|
1822
|
+
...base.dependentSchemas,
|
|
1823
|
+
...override.dependentSchemas
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
return merged;
|
|
1827
|
+
}
|
|
1828
|
+
function getSubSchema(schema, key) {
|
|
1829
|
+
if (schema.properties && schema.properties[key]) {
|
|
1830
|
+
return {
|
|
1831
|
+
schema: schema.properties[key],
|
|
1832
|
+
keywordLocationToken: `properties/${key}`
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
if (schema.patternProperties) {
|
|
1836
|
+
for (const [pattern, subschema] of Object.entries(
|
|
1837
|
+
schema.patternProperties
|
|
1838
|
+
)) {
|
|
1839
|
+
if (safeRegexTest(pattern, key)) {
|
|
1840
|
+
return {
|
|
1841
|
+
schema: subschema,
|
|
1842
|
+
keywordLocationToken: `patternProperties/${pattern}`
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
|
|
1848
|
+
return {
|
|
1849
|
+
schema: typeof schema.additionalProperties === "object" ? schema.additionalProperties : {},
|
|
1850
|
+
keywordLocationToken: "additionalProperties"
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
if (schema.items || schema.prefixItems) {
|
|
1854
|
+
const index = parseInt(key, 10);
|
|
1855
|
+
if (!isNaN(index)) {
|
|
1856
|
+
if (schema.prefixItems && index < schema.prefixItems.length) {
|
|
1857
|
+
return {
|
|
1858
|
+
schema: schema.prefixItems[index],
|
|
1859
|
+
keywordLocationToken: `prefixItems/${index}`
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
if (schema.items) {
|
|
1863
|
+
return {
|
|
1864
|
+
schema: schema.items,
|
|
1865
|
+
keywordLocationToken: "items"
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
return {
|
|
1871
|
+
schema: {},
|
|
1872
|
+
keywordLocationToken: ""
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
function getDefaultValue(schema) {
|
|
1876
|
+
if (schema.const !== void 0) return schema.const;
|
|
1877
|
+
if (schema.default !== void 0) return schema.default;
|
|
1878
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
1879
|
+
switch (type) {
|
|
1880
|
+
case "string":
|
|
1881
|
+
return "";
|
|
1882
|
+
case "number":
|
|
1883
|
+
case "integer":
|
|
1884
|
+
return 0;
|
|
1885
|
+
case "boolean":
|
|
1886
|
+
return false;
|
|
1887
|
+
case "null":
|
|
1888
|
+
return null;
|
|
1889
|
+
case "object": {
|
|
1890
|
+
const obj = {};
|
|
1891
|
+
if (schema.properties) {
|
|
1892
|
+
for (const [key, subschema] of Object.entries(schema.properties)) {
|
|
1893
|
+
if (schema.required?.includes(key)) {
|
|
1894
|
+
obj[key] = getDefaultValue(subschema);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return obj;
|
|
1899
|
+
}
|
|
1900
|
+
case "array":
|
|
1901
|
+
return [];
|
|
1902
|
+
default:
|
|
1903
|
+
if (schema.properties) {
|
|
1904
|
+
return getDefaultValue({ ...schema, type: "object" });
|
|
1905
|
+
}
|
|
1906
|
+
return void 0;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/render.ts
|
|
1911
|
+
var ROOT_PATH = "";
|
|
1912
|
+
function normalizeRootPath(path) {
|
|
1913
|
+
return path === "#" ? ROOT_PATH : path;
|
|
1914
|
+
}
|
|
1915
|
+
var SchemaRuntime = class {
|
|
1916
|
+
validator;
|
|
1917
|
+
watchers = {};
|
|
1918
|
+
globalWatchers = /* @__PURE__ */ new Set();
|
|
1919
|
+
// Reverse dependency index: path -> nodes that depend on this path's value
|
|
1920
|
+
dependentsMap = /* @__PURE__ */ new Map();
|
|
1921
|
+
// Track nodes currently being updated to prevent circular updates
|
|
1922
|
+
updatingNodes = /* @__PURE__ */ new Set();
|
|
1923
|
+
root;
|
|
1924
|
+
value;
|
|
1925
|
+
version = 0;
|
|
1926
|
+
rootSchema = {};
|
|
1927
|
+
/**
|
|
1928
|
+
* Create a new SchemaRuntime instance.
|
|
1929
|
+
*
|
|
1930
|
+
* @param validator - The validator instance for schema validation
|
|
1931
|
+
* @param schema - The JSON Schema definition (will be normalized and dereferenced)
|
|
1932
|
+
* @param value - The initial data value to manage
|
|
1933
|
+
*
|
|
1934
|
+
* @example
|
|
1935
|
+
* const validator = new Validator();
|
|
1936
|
+
* const schema = { type: "object", properties: { name: { type: "string" } } };
|
|
1937
|
+
* const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
|
|
1938
|
+
*/
|
|
1939
|
+
constructor(validator, schema, value) {
|
|
1940
|
+
const normalized = normalizeSchema(schema);
|
|
1941
|
+
this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
|
|
1942
|
+
this.validator = validator;
|
|
1943
|
+
this.value = value;
|
|
1944
|
+
this.root = this.createEmptyNode("", "#");
|
|
1945
|
+
this.buildNode(this.root, this.rootSchema);
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Collect all dependencies for a node's schema.
|
|
1949
|
+
*
|
|
1950
|
+
* Key insight: We extract paths from condition keywords (if, oneOf, anyOf)
|
|
1951
|
+
* using extractReferencedPaths, but for then/else/allOf keywords, we recursively
|
|
1952
|
+
* call collectDependencies to ensure proper dependency isolation between
|
|
1953
|
+
* parent and child nodes.
|
|
1954
|
+
*/
|
|
1955
|
+
collectDependencies(schema, instanceLocation) {
|
|
1956
|
+
const deps = /* @__PURE__ */ new Set();
|
|
1957
|
+
if (schema.required) {
|
|
1958
|
+
for (const req of schema.required) {
|
|
1959
|
+
deps.add(resolveAbsolutePath(instanceLocation, `/${req}`));
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (schema.dependentRequired) {
|
|
1963
|
+
for (const [prop, reqs] of Object.entries(schema.dependentRequired)) {
|
|
1964
|
+
deps.add(resolveAbsolutePath(instanceLocation, `/${prop}`));
|
|
1965
|
+
for (const req of reqs) {
|
|
1966
|
+
deps.add(resolveAbsolutePath(instanceLocation, `/${req}`));
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
if (schema.dependentSchemas) {
|
|
1971
|
+
for (const [prop, subSchema] of Object.entries(schema.dependentSchemas)) {
|
|
1972
|
+
deps.add(resolveAbsolutePath(instanceLocation, `/${prop}`));
|
|
1973
|
+
const subDeps = this.collectDependencies(subSchema, instanceLocation);
|
|
1974
|
+
subDeps.forEach((d) => deps.add(d));
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
if (schema.if) {
|
|
1978
|
+
const relativePaths = extractReferencedPaths(schema.if, "");
|
|
1979
|
+
for (const relPath of relativePaths) {
|
|
1980
|
+
deps.add(resolveAbsolutePath(instanceLocation, relPath));
|
|
1981
|
+
}
|
|
1982
|
+
if (schema.then) {
|
|
1983
|
+
const thenDeps = this.collectDependencies(
|
|
1984
|
+
schema.then,
|
|
1985
|
+
instanceLocation
|
|
1986
|
+
);
|
|
1987
|
+
thenDeps.forEach((d) => deps.add(d));
|
|
1988
|
+
}
|
|
1989
|
+
if (schema.else) {
|
|
1990
|
+
const elseDeps = this.collectDependencies(
|
|
1991
|
+
schema.else,
|
|
1992
|
+
instanceLocation
|
|
1993
|
+
);
|
|
1994
|
+
elseDeps.forEach((d) => deps.add(d));
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
if (schema.oneOf) {
|
|
1998
|
+
for (const subSchema of schema.oneOf) {
|
|
1999
|
+
const relativePaths = extractReferencedPaths(subSchema, "");
|
|
2000
|
+
for (const relPath of relativePaths) {
|
|
2001
|
+
deps.add(resolveAbsolutePath(instanceLocation, relPath));
|
|
2002
|
+
}
|
|
2003
|
+
const subDeps = this.collectDependencies(subSchema, instanceLocation);
|
|
2004
|
+
subDeps.forEach((d) => deps.add(d));
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
if (schema.anyOf) {
|
|
2008
|
+
for (const subSchema of schema.anyOf) {
|
|
2009
|
+
const relativePaths = extractReferencedPaths(subSchema, "");
|
|
2010
|
+
for (const relPath of relativePaths) {
|
|
2011
|
+
deps.add(resolveAbsolutePath(instanceLocation, relPath));
|
|
2012
|
+
}
|
|
2013
|
+
const subDeps = this.collectDependencies(subSchema, instanceLocation);
|
|
2014
|
+
subDeps.forEach((d) => deps.add(d));
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
if (schema.allOf) {
|
|
2018
|
+
for (const subSchema of schema.allOf) {
|
|
2019
|
+
const subDeps = this.collectDependencies(subSchema, instanceLocation);
|
|
2020
|
+
subDeps.forEach((d) => deps.add(d));
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return deps;
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Register a node as dependent on a path
|
|
2027
|
+
*/
|
|
2028
|
+
registerDependent(path, node) {
|
|
2029
|
+
let dependents = this.dependentsMap.get(path);
|
|
2030
|
+
if (!dependents) {
|
|
2031
|
+
dependents = /* @__PURE__ */ new Set();
|
|
2032
|
+
this.dependentsMap.set(path, dependents);
|
|
2033
|
+
}
|
|
2034
|
+
dependents.add(node);
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Unregister a node from a dependency path
|
|
2038
|
+
*/
|
|
2039
|
+
unregisterDependent(path, node) {
|
|
2040
|
+
const dependents = this.dependentsMap.get(path);
|
|
2041
|
+
if (dependents) {
|
|
2042
|
+
dependents.delete(node);
|
|
2043
|
+
if (dependents.size === 0) {
|
|
2044
|
+
this.dependentsMap.delete(path);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Unregister all dependencies for a node and its children.
|
|
2050
|
+
* Note: Does NOT clean up external watchers - callers are responsible
|
|
2051
|
+
* for calling unsubscribe() when they no longer need updates.
|
|
2052
|
+
*/
|
|
2053
|
+
unregisterNodeDependencies(node) {
|
|
2054
|
+
if (node.dependencies) {
|
|
2055
|
+
for (const depPath of node.dependencies) {
|
|
2056
|
+
this.unregisterDependent(depPath, node);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
if (node.children) {
|
|
2060
|
+
for (const child of node.children) {
|
|
2061
|
+
this.unregisterNodeDependencies(child);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Create an empty FieldNode with default values.
|
|
2067
|
+
*/
|
|
2068
|
+
createEmptyNode(instanceLocation, keywordLocation) {
|
|
2069
|
+
return {
|
|
2070
|
+
type: "null",
|
|
2071
|
+
schema: {},
|
|
2072
|
+
version: -1,
|
|
2073
|
+
instanceLocation,
|
|
2074
|
+
keywordLocation,
|
|
2075
|
+
originalSchema: {},
|
|
2076
|
+
canRemove: false,
|
|
2077
|
+
canAdd: false,
|
|
2078
|
+
children: []
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Reconcile the tree starting from the specified path.
|
|
2083
|
+
* Uses findNearestExistingNode to find the target node (or parent if path doesn't exist).
|
|
2084
|
+
* Only rebuilds the affected subtree, not the entire tree.
|
|
2085
|
+
*/
|
|
2086
|
+
reconcile(path) {
|
|
2087
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2088
|
+
const targetNode = this.findNearestExistingNode(normalizedPath);
|
|
2089
|
+
if (!targetNode) {
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
this.buildNode(targetNode, targetNode.originalSchema);
|
|
2093
|
+
}
|
|
2094
|
+
/**
|
|
2095
|
+
* Get the current version number.
|
|
2096
|
+
* The version increments on every notify call (value, schema, or error changes).
|
|
2097
|
+
* Useful for detecting if the runtime state has changed.
|
|
2098
|
+
*
|
|
2099
|
+
* @returns The current version number
|
|
2100
|
+
*/
|
|
2101
|
+
getVersion() {
|
|
2102
|
+
return this.version;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Subscribe to changes at a specific path.
|
|
2106
|
+
* The callback is invoked when the value, schema, or error at the path changes.
|
|
2107
|
+
*
|
|
2108
|
+
* @param path - The JSON Pointer path to watch (e.g., "/user/name", "" for root)
|
|
2109
|
+
* @param cb - Callback function invoked with the change event
|
|
2110
|
+
* @returns Unsubscribe function to remove the listener
|
|
2111
|
+
*
|
|
2112
|
+
* @example
|
|
2113
|
+
* const unsubscribe = runtime.subscribe("/name", (event) => {
|
|
2114
|
+
* console.log(`${event.type} changed at ${event.path}`);
|
|
2115
|
+
* });
|
|
2116
|
+
* // Later: unsubscribe();
|
|
2117
|
+
*/
|
|
2118
|
+
subscribe(path, cb) {
|
|
2119
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2120
|
+
if (!this.watchers[normalizedPath]) {
|
|
2121
|
+
this.watchers[normalizedPath] = /* @__PURE__ */ new Set();
|
|
2122
|
+
}
|
|
2123
|
+
this.watchers[normalizedPath].add(cb);
|
|
2124
|
+
return () => {
|
|
2125
|
+
const watcherSet = this.watchers[normalizedPath];
|
|
2126
|
+
if (watcherSet) {
|
|
2127
|
+
watcherSet.delete(cb);
|
|
2128
|
+
if (watcherSet.size === 0) {
|
|
2129
|
+
delete this.watchers[normalizedPath];
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Subscribe to all events in the runtime.
|
|
2136
|
+
* The callback is invoked for any change at any path.
|
|
2137
|
+
*
|
|
2138
|
+
* @param cb - Callback function invoked with every change event
|
|
2139
|
+
* @returns Unsubscribe function to remove the listener
|
|
2140
|
+
*/
|
|
2141
|
+
subscribeAll(cb) {
|
|
2142
|
+
this.globalWatchers.add(cb);
|
|
2143
|
+
return () => {
|
|
2144
|
+
this.globalWatchers.delete(cb);
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Emit a change event to all relevant subscribers.
|
|
2149
|
+
* Increments the version number and notifies both path-specific and global watchers.
|
|
2150
|
+
*
|
|
2151
|
+
* @param event - The change event containing type and path
|
|
2152
|
+
*/
|
|
2153
|
+
notify(event) {
|
|
2154
|
+
this.version++;
|
|
2155
|
+
const normalizedPath = normalizeRootPath(event.path);
|
|
2156
|
+
const watchers = this.watchers[normalizedPath];
|
|
2157
|
+
if (watchers) {
|
|
2158
|
+
for (const cb of watchers) {
|
|
2159
|
+
try {
|
|
2160
|
+
cb(event);
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
console.error("SchemaRuntime: watcher callback error:", err);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
for (const cb of this.globalWatchers) {
|
|
2167
|
+
try {
|
|
2168
|
+
cb(event);
|
|
2169
|
+
} catch (err) {
|
|
2170
|
+
console.error("SchemaRuntime: global watcher callback error:", err);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Update the entire schema.
|
|
2176
|
+
* This triggers a full rebuild of the node tree while preserving the current value.
|
|
2177
|
+
*/
|
|
2178
|
+
setSchema(schema) {
|
|
2179
|
+
const normalized = normalizeSchema(schema);
|
|
2180
|
+
this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
|
|
2181
|
+
this.root = this.createEmptyNode("", "#");
|
|
2182
|
+
this.buildNode(this.root, this.rootSchema);
|
|
2183
|
+
this.notify({ type: "schema", path: ROOT_PATH });
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Get the value at a specific path.
|
|
2187
|
+
*
|
|
2188
|
+
* @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
|
|
2189
|
+
* @returns The value at the path, or undefined if not found
|
|
2190
|
+
*
|
|
2191
|
+
* @example
|
|
2192
|
+
* runtime.getValue(""); // returns entire root value
|
|
2193
|
+
* runtime.getValue("/name"); // returns value at /name
|
|
2194
|
+
*/
|
|
2195
|
+
getValue(path) {
|
|
2196
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2197
|
+
if (normalizedPath === ROOT_PATH) return this.value;
|
|
2198
|
+
return getJsonPointer(this.value, normalizedPath);
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Remove a node at the specified path.
|
|
2202
|
+
* This deletes the value from the data structure (array splice or object delete).
|
|
2203
|
+
* @param path - The path to remove
|
|
2204
|
+
* @returns true if successful, false if the path cannot be removed
|
|
2205
|
+
*/
|
|
2206
|
+
removeValue(path) {
|
|
2207
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2208
|
+
if (normalizedPath === ROOT_PATH) {
|
|
2209
|
+
return false;
|
|
2210
|
+
}
|
|
2211
|
+
const node = this.findNode(normalizedPath);
|
|
2212
|
+
if (!node || !node.canRemove) {
|
|
2213
|
+
return false;
|
|
2214
|
+
}
|
|
2215
|
+
const success = removeJsonPointer(this.value, normalizedPath);
|
|
2216
|
+
if (!success) {
|
|
2217
|
+
return false;
|
|
2218
|
+
}
|
|
2219
|
+
const lastSlash = normalizedPath.lastIndexOf("/");
|
|
2220
|
+
const parentPath = lastSlash <= 0 ? ROOT_PATH : normalizedPath.substring(0, lastSlash);
|
|
2221
|
+
this.reconcile(parentPath);
|
|
2222
|
+
this.notify({ type: "value", path: parentPath });
|
|
2223
|
+
return true;
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Add a new child to an array or object at the specified parent path.
|
|
2227
|
+
* For arrays, appends a new item with default value based on items schema.
|
|
2228
|
+
* For objects, adds a new property with the given key and default value based on additionalProperties schema.
|
|
2229
|
+
* @param parentPath - The path to the parent array or object
|
|
2230
|
+
* @param key - For objects: the property key. For arrays: optional, ignored (appends to end)
|
|
2231
|
+
* @param initialValue - Optional initial value to set. If not provided, uses default from schema.
|
|
2232
|
+
* @returns true if successful, false if cannot add
|
|
2233
|
+
*/
|
|
2234
|
+
addValue(parentPath, key, initialValue) {
|
|
2235
|
+
const normalizedPath = normalizeRootPath(parentPath);
|
|
2236
|
+
const parentNode = this.findNode(normalizedPath);
|
|
2237
|
+
if (!parentNode || !parentNode.canAdd) {
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
const parentValue = this.getValue(normalizedPath);
|
|
2241
|
+
const parentSchema = parentNode.schema;
|
|
2242
|
+
if (parentNode.type === "array" && Array.isArray(parentValue)) {
|
|
2243
|
+
const newIndex = parentValue.length;
|
|
2244
|
+
const { schema: subschema } = getSubSchema(
|
|
2245
|
+
parentSchema,
|
|
2246
|
+
String(newIndex)
|
|
2247
|
+
);
|
|
2248
|
+
const defaultValue = initialValue !== void 0 ? initialValue : getDefaultValue(subschema);
|
|
2249
|
+
const itemPath = jsonPointerJoin(normalizedPath, String(newIndex));
|
|
2250
|
+
return this.setValue(itemPath, defaultValue);
|
|
2251
|
+
} else if (parentNode.type === "object" && parentValue && typeof parentValue === "object") {
|
|
2252
|
+
if (!key) {
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
const { schema: subschema } = getSubSchema(parentSchema, key);
|
|
2256
|
+
if (!parentSchema.additionalProperties) {
|
|
2257
|
+
return false;
|
|
2258
|
+
}
|
|
2259
|
+
const defaultValue = initialValue !== void 0 ? initialValue : getDefaultValue(subschema);
|
|
2260
|
+
const propertyPath = jsonPointerJoin(normalizedPath, key);
|
|
2261
|
+
return this.setValue(propertyPath, defaultValue);
|
|
2262
|
+
}
|
|
2263
|
+
return false;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Set the value at a specific path.
|
|
2267
|
+
* Creates intermediate containers (objects/arrays) as needed.
|
|
2268
|
+
* Triggers reconciliation and notifies subscribers.
|
|
2269
|
+
*
|
|
2270
|
+
* @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
|
|
2271
|
+
* @param value - The new value to set
|
|
2272
|
+
* @returns true if successful, false if the path cannot be set
|
|
2273
|
+
*
|
|
2274
|
+
* @example
|
|
2275
|
+
* runtime.setValue("/name", "Bob"); // set name to "Bob"
|
|
2276
|
+
* runtime.setValue("", { name: "Alice" }); // replace entire root value
|
|
2277
|
+
*/
|
|
2278
|
+
setValue(path, value) {
|
|
2279
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2280
|
+
if (normalizedPath === ROOT_PATH) {
|
|
2281
|
+
this.value = value;
|
|
2282
|
+
} else {
|
|
2283
|
+
const success = setJsonPointer(this.value, normalizedPath, value);
|
|
2284
|
+
if (!success) return false;
|
|
2285
|
+
}
|
|
2286
|
+
this.reconcile(normalizedPath);
|
|
2287
|
+
this.notify({ type: "value", path: normalizedPath });
|
|
2288
|
+
return true;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Find the FieldNode at a specific path.
|
|
2292
|
+
* Returns the node tree representation that includes schema, type, error, and children.
|
|
2293
|
+
*
|
|
2294
|
+
* @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
|
|
2295
|
+
* @returns The FieldNode at the path, or undefined if not found
|
|
2296
|
+
*
|
|
2297
|
+
* @example
|
|
2298
|
+
* const node = runtime.findNode("/name");
|
|
2299
|
+
* console.log(node?.schema, node?.type, node?.error);
|
|
2300
|
+
*/
|
|
2301
|
+
findNode(path) {
|
|
2302
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2303
|
+
if (normalizedPath === ROOT_PATH) return this.root;
|
|
2304
|
+
const segments = parseJsonPointer(normalizedPath);
|
|
2305
|
+
let current = this.root;
|
|
2306
|
+
for (const segment of segments) {
|
|
2307
|
+
if (!current?.children) return void 0;
|
|
2308
|
+
const expectedPath = jsonPointerJoin(current.instanceLocation, segment);
|
|
2309
|
+
const found = current.children?.find(
|
|
2310
|
+
(child) => child.instanceLocation === expectedPath
|
|
2311
|
+
);
|
|
2312
|
+
if (!found) return void 0;
|
|
2313
|
+
current = found;
|
|
2314
|
+
}
|
|
2315
|
+
return current;
|
|
2316
|
+
}
|
|
2317
|
+
findNearestExistingNode(path) {
|
|
2318
|
+
const normalizedPath = normalizeRootPath(path);
|
|
2319
|
+
let currentPath = normalizedPath;
|
|
2320
|
+
while (currentPath !== ROOT_PATH) {
|
|
2321
|
+
const node = this.findNode(currentPath);
|
|
2322
|
+
if (node) return node;
|
|
2323
|
+
const lastSlash = currentPath.lastIndexOf("/");
|
|
2324
|
+
currentPath = lastSlash <= 0 ? ROOT_PATH : currentPath.substring(0, lastSlash);
|
|
2325
|
+
}
|
|
2326
|
+
return this.root;
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Build/update a FieldNode in place.
|
|
2330
|
+
* Updates the node's schema, type, error, and children based on the current value.
|
|
2331
|
+
* @param schema - Optional. If provided, updates node.originalSchema. Otherwise uses existing.
|
|
2332
|
+
*/
|
|
2333
|
+
buildNode(node, schema, options = {}) {
|
|
2334
|
+
const { keywordLocation, instanceLocation } = node;
|
|
2335
|
+
const value = this.getValue(instanceLocation);
|
|
2336
|
+
if (value === void 0) {
|
|
2337
|
+
const defaultValue = getDefaultValue(schema || node.originalSchema);
|
|
2338
|
+
this.setValue(instanceLocation, defaultValue);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
if (this.updatingNodes.has(instanceLocation)) {
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const updatedNodes = options.updatedNodes || /* @__PURE__ */ new Set();
|
|
2345
|
+
if (updatedNodes.has(instanceLocation)) {
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
this.updatingNodes.add(instanceLocation);
|
|
2349
|
+
const schemaChanged = schema !== void 0 && !deepEqual(schema, node.originalSchema);
|
|
2350
|
+
if (schemaChanged) {
|
|
2351
|
+
node.originalSchema = schema;
|
|
2352
|
+
const dependencies = this.collectDependencies(
|
|
2353
|
+
node.originalSchema,
|
|
2354
|
+
instanceLocation
|
|
2355
|
+
);
|
|
2356
|
+
for (const depPath of node.dependencies || []) {
|
|
2357
|
+
this.unregisterDependent(depPath, node);
|
|
2358
|
+
}
|
|
2359
|
+
node.dependencies = dependencies;
|
|
2360
|
+
if (!options.skipDependencyRegistration) {
|
|
2361
|
+
for (const depPath of dependencies) {
|
|
2362
|
+
this.registerDependent(depPath, node);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
const { type, effectiveSchema, error } = resolveEffectiveSchema(
|
|
2367
|
+
this.validator,
|
|
2368
|
+
node.originalSchema,
|
|
2369
|
+
value,
|
|
2370
|
+
keywordLocation,
|
|
2371
|
+
instanceLocation
|
|
2372
|
+
);
|
|
2373
|
+
const effectiveSchemaChanged = !deepEqual(effectiveSchema, node.schema) || type !== node.type;
|
|
2374
|
+
const errorChanged = !deepEqual(error, node.error);
|
|
2375
|
+
node.schema = effectiveSchema;
|
|
2376
|
+
node.type = type;
|
|
2377
|
+
node.error = error;
|
|
2378
|
+
node.version++;
|
|
2379
|
+
const oldChildrenMap = /* @__PURE__ */ new Map();
|
|
2380
|
+
if (node.children) {
|
|
2381
|
+
for (const child of node.children) {
|
|
2382
|
+
oldChildrenMap.set(child.instanceLocation, child);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
const newChildren = [];
|
|
2386
|
+
const processChild = (childKey, childSchema, childkeywordLocation, canRemove = false) => {
|
|
2387
|
+
const childinstanceLocation = jsonPointerJoin(instanceLocation, childKey);
|
|
2388
|
+
let childNode = oldChildrenMap.get(childinstanceLocation);
|
|
2389
|
+
if (childNode) {
|
|
2390
|
+
oldChildrenMap.delete(childinstanceLocation);
|
|
2391
|
+
childNode.keywordLocation = childkeywordLocation;
|
|
2392
|
+
} else {
|
|
2393
|
+
childNode = this.createEmptyNode(
|
|
2394
|
+
childinstanceLocation,
|
|
2395
|
+
childkeywordLocation
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
childNode.canRemove = canRemove;
|
|
2399
|
+
this.buildNode(childNode, childSchema, options);
|
|
2400
|
+
newChildren.push(childNode);
|
|
2401
|
+
};
|
|
2402
|
+
switch (type) {
|
|
2403
|
+
case "object": {
|
|
2404
|
+
const valueKeys = value && typeof value === "object" ? Object.keys(value) : [];
|
|
2405
|
+
const processedKeys = /* @__PURE__ */ new Set();
|
|
2406
|
+
node.canAdd = !!effectiveSchema.additionalProperties;
|
|
2407
|
+
if (effectiveSchema.properties) {
|
|
2408
|
+
for (const [key, subschema] of Object.entries(
|
|
2409
|
+
effectiveSchema.properties
|
|
2410
|
+
)) {
|
|
2411
|
+
processedKeys.add(key);
|
|
2412
|
+
processChild(
|
|
2413
|
+
key,
|
|
2414
|
+
subschema,
|
|
2415
|
+
`${keywordLocation}/properties/${key}`,
|
|
2416
|
+
false
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (effectiveSchema.patternProperties) {
|
|
2421
|
+
for (const [pattern, subschema] of Object.entries(
|
|
2422
|
+
effectiveSchema.patternProperties
|
|
2423
|
+
)) {
|
|
2424
|
+
for (const key of valueKeys) {
|
|
2425
|
+
if (safeRegexTest(pattern, key) && !processedKeys.has(key)) {
|
|
2426
|
+
processedKeys.add(key);
|
|
2427
|
+
processChild(
|
|
2428
|
+
key,
|
|
2429
|
+
subschema,
|
|
2430
|
+
`${keywordLocation}/patternProperties/${jsonPointerEscape(pattern)}`,
|
|
2431
|
+
true
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
if (effectiveSchema.additionalProperties) {
|
|
2438
|
+
const subschema = typeof effectiveSchema.additionalProperties === "object" ? effectiveSchema.additionalProperties : {};
|
|
2439
|
+
for (const key of valueKeys) {
|
|
2440
|
+
if (!processedKeys.has(key)) {
|
|
2441
|
+
processChild(
|
|
2442
|
+
key,
|
|
2443
|
+
subschema,
|
|
2444
|
+
`${keywordLocation}/additionalProperties`,
|
|
2445
|
+
true
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
break;
|
|
2451
|
+
}
|
|
2452
|
+
case "array": {
|
|
2453
|
+
node.canAdd = !!effectiveSchema.items;
|
|
2454
|
+
if (Array.isArray(value)) {
|
|
2455
|
+
let prefixItemsLength = 0;
|
|
2456
|
+
if (effectiveSchema.prefixItems) {
|
|
2457
|
+
prefixItemsLength = effectiveSchema.prefixItems.length;
|
|
2458
|
+
for (let i = 0; i < Math.min(value.length, prefixItemsLength); i++) {
|
|
2459
|
+
processChild(
|
|
2460
|
+
String(i),
|
|
2461
|
+
effectiveSchema.prefixItems[i],
|
|
2462
|
+
`${keywordLocation}/prefixItems/${i}`,
|
|
2463
|
+
false
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
if (effectiveSchema.items && value.length > prefixItemsLength) {
|
|
2468
|
+
for (let i = prefixItemsLength; i < value.length; i++) {
|
|
2469
|
+
processChild(
|
|
2470
|
+
String(i),
|
|
2471
|
+
effectiveSchema.items,
|
|
2472
|
+
`${keywordLocation}/items`,
|
|
2473
|
+
true
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
break;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
for (const oldChild of oldChildrenMap.values()) {
|
|
2482
|
+
this.unregisterNodeDependencies(oldChild);
|
|
2483
|
+
}
|
|
2484
|
+
node.children = newChildren;
|
|
2485
|
+
if (effectiveSchemaChanged) {
|
|
2486
|
+
this.notify({ type: "schema", path: node.instanceLocation });
|
|
2487
|
+
}
|
|
2488
|
+
if (errorChanged) {
|
|
2489
|
+
this.notify({ type: "error", path: node.instanceLocation });
|
|
2490
|
+
}
|
|
2491
|
+
updatedNodes.add(instanceLocation);
|
|
2492
|
+
this.updatingNodes.delete(instanceLocation);
|
|
2493
|
+
const dependentNodes = this.dependentsMap.get(instanceLocation);
|
|
2494
|
+
if (dependentNodes) {
|
|
2495
|
+
for (const dependentNode of dependentNodes) {
|
|
2496
|
+
this.buildNode(dependentNode, void 0, {
|
|
2497
|
+
...options,
|
|
2498
|
+
updatedNodes
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
|
|
2505
|
+
exports.DRAFT_URIS = DRAFT_URIS;
|
|
2506
|
+
exports.MESSAGES = MESSAGES;
|
|
2507
|
+
exports.SchemaRuntime = SchemaRuntime;
|
|
2508
|
+
exports.StringFormatValidator = StringFormatValidator;
|
|
2509
|
+
exports.Validator = Validator;
|
|
2510
|
+
exports.deepEqual = deepEqual;
|
|
2511
|
+
exports.defaultErrorFormatter = defaultErrorFormatter;
|
|
2512
|
+
exports.detectSchemaDraft = detectSchemaDraft;
|
|
2513
|
+
exports.detectSchemaType = detectSchemaType;
|
|
2514
|
+
exports.get = get;
|
|
2515
|
+
exports.getJsonPointer = getJsonPointer;
|
|
2516
|
+
exports.jsonPointerEscape = jsonPointerEscape;
|
|
2517
|
+
exports.jsonPointerJoin = jsonPointerJoin;
|
|
2518
|
+
exports.jsonPointerUnescape = jsonPointerUnescape;
|
|
2519
|
+
exports.matchSchemaType = matchSchemaType;
|
|
2520
|
+
exports.normalizeSchema = normalizeSchema;
|
|
2521
|
+
exports.parseJsonPointer = parseJsonPointer;
|
|
2522
|
+
exports.removeJsonPointer = removeJsonPointer;
|
|
2523
|
+
exports.resolveAbsolutePath = resolveAbsolutePath;
|
|
2524
|
+
exports.safeRegexTest = safeRegexTest;
|
|
2525
|
+
exports.setJsonPointer = setJsonPointer;
|
|
2526
|
+
exports.stringFormatValidator = stringFormatValidator;
|
|
2527
|
+
exports.validateSchema = validateSchema;
|
|
2528
|
+
//# sourceMappingURL=index.cjs.map
|
|
2529
|
+
//# sourceMappingURL=index.cjs.map
|