@x0k/json-schema-merge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +181 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/array.d.ts +19 -0
- package/dist/lib/array.js +133 -0
- package/dist/lib/function.d.ts +1 -0
- package/dist/lib/function.js +3 -0
- package/dist/lib/json-schema/compare/compare.d.ts +9 -0
- package/dist/lib/json-schema/compare/compare.js +205 -0
- package/dist/lib/json-schema/compare/index.d.ts +1 -0
- package/dist/lib/json-schema/compare/index.js +1 -0
- package/dist/lib/json-schema/index.d.ts +5 -0
- package/dist/lib/json-schema/index.js +5 -0
- package/dist/lib/json-schema/json-schema.d.ts +37 -0
- package/dist/lib/json-schema/json-schema.js +56 -0
- package/dist/lib/json-schema/merge/all-of-merge.d.ts +3 -0
- package/dist/lib/json-schema/merge/all-of-merge.js +29 -0
- package/dist/lib/json-schema/merge/index.d.ts +3 -0
- package/dist/lib/json-schema/merge/index.js +3 -0
- package/dist/lib/json-schema/merge/merge.d.ts +91 -0
- package/dist/lib/json-schema/merge/merge.js +554 -0
- package/dist/lib/json-schema/merge/patterns.d.ts +2 -0
- package/dist/lib/json-schema/merge/patterns.js +6 -0
- package/dist/lib/json-schema/transform.d.ts +4 -0
- package/dist/lib/json-schema/transform.js +72 -0
- package/dist/lib/json-schema/traverse.d.ts +26 -0
- package/dist/lib/json-schema/traverse.js +1 -0
- package/dist/lib/math.d.ts +2 -0
- package/dist/lib/math.js +2 -0
- package/dist/lib/memoize.d.ts +7 -0
- package/dist/lib/memoize.js +11 -0
- package/dist/lib/object.d.ts +2 -0
- package/dist/lib/object.js +12 -0
- package/dist/lib/ord.d.ts +9 -0
- package/dist/lib/ord.js +7 -0
- package/dist/lib/traverser.d.ts +4 -0
- package/dist/lib/traverser.js +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import { intersection, union, } from "../../array.js";
|
|
2
|
+
import { identity } from "../../function.js";
|
|
3
|
+
import { lcm } from "../../math.js";
|
|
4
|
+
import { isAllowAnySchema } from "../json-schema.js";
|
|
5
|
+
import { simplePatternsMerger } from "./patterns.js";
|
|
6
|
+
function* createPairCombinations(l, r, action) {
|
|
7
|
+
const ll = l.length;
|
|
8
|
+
const rl = r.length;
|
|
9
|
+
if (ll > 0 && rl > 0) {
|
|
10
|
+
for (let i = 0; i < ll; i++) {
|
|
11
|
+
const lv = l[i];
|
|
12
|
+
for (let j = 0; j < rl; j++) {
|
|
13
|
+
yield action(lv, r[j]);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function mergeBooleans(l, r) {
|
|
19
|
+
return l || r;
|
|
20
|
+
}
|
|
21
|
+
function createRecordsMerge(merge) {
|
|
22
|
+
return (left, right) => {
|
|
23
|
+
const target = { ...left };
|
|
24
|
+
const keys = Object.keys(right);
|
|
25
|
+
const l = keys.length;
|
|
26
|
+
for (let i = 0; i < l; i++) {
|
|
27
|
+
const key = keys[i];
|
|
28
|
+
target[key] =
|
|
29
|
+
left[key] === undefined ? right[key] : merge(left[key], right[key]);
|
|
30
|
+
}
|
|
31
|
+
return target;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createMap(items) {
|
|
35
|
+
const map = new Map();
|
|
36
|
+
for (const pair of items) {
|
|
37
|
+
for (const key of pair[0]) {
|
|
38
|
+
map.set(key, pair[1]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return map;
|
|
42
|
+
}
|
|
43
|
+
function assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, key, value) {
|
|
44
|
+
if (value === undefined || isAllowAnySchema(value)) {
|
|
45
|
+
delete target[key];
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
target[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const PROPERTIES_ASSIGNER_KEYS = [
|
|
52
|
+
"properties",
|
|
53
|
+
"patternProperties",
|
|
54
|
+
"additionalProperties",
|
|
55
|
+
];
|
|
56
|
+
function compilePatterns(patterns) {
|
|
57
|
+
const keys = Object.keys(patterns);
|
|
58
|
+
const l = keys.length;
|
|
59
|
+
const result = [];
|
|
60
|
+
for (let i = 0; i < l; i++) {
|
|
61
|
+
const source = keys[i];
|
|
62
|
+
result.push({
|
|
63
|
+
regExp: new RegExp(source),
|
|
64
|
+
schema: patterns[source],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return [result, keys];
|
|
68
|
+
}
|
|
69
|
+
const EMPTY_PATTERNS_AND_KEYS = [[], []];
|
|
70
|
+
/**
|
|
71
|
+
* @returns `true` when `false` schema occurred
|
|
72
|
+
*/
|
|
73
|
+
function appendKeyConstraints(target, key, patterns) {
|
|
74
|
+
const l = patterns.length;
|
|
75
|
+
for (let i = 0; i < l; i++) {
|
|
76
|
+
const p = patterns[i];
|
|
77
|
+
if (!p.regExp.test(key)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const s = p.schema;
|
|
81
|
+
if (s === false) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
target.push(s);
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const ITEMS_ASSIGNER_KEYS = [
|
|
89
|
+
"items",
|
|
90
|
+
"additionalItems",
|
|
91
|
+
];
|
|
92
|
+
const CONDITION_ASSIGNER_KEYS = [
|
|
93
|
+
"if",
|
|
94
|
+
"then",
|
|
95
|
+
"else",
|
|
96
|
+
];
|
|
97
|
+
function assignCondition(target, source) {
|
|
98
|
+
if (source.if !== undefined) {
|
|
99
|
+
target.if = source.if;
|
|
100
|
+
}
|
|
101
|
+
if (source.then !== undefined) {
|
|
102
|
+
target.then = source.then;
|
|
103
|
+
}
|
|
104
|
+
if (source.else !== undefined) {
|
|
105
|
+
target.else = source.else;
|
|
106
|
+
}
|
|
107
|
+
return target;
|
|
108
|
+
}
|
|
109
|
+
function intersectSchemaTypes(a, b) {
|
|
110
|
+
if (a === b) {
|
|
111
|
+
return a;
|
|
112
|
+
}
|
|
113
|
+
switch (a) {
|
|
114
|
+
case "number": {
|
|
115
|
+
if (b === "integer") {
|
|
116
|
+
return "integer";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// eslint-disable-next-line no-fallthrough
|
|
120
|
+
case "integer": {
|
|
121
|
+
if (b === "number") {
|
|
122
|
+
return "integer";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// eslint-disable-next-line no-fallthrough
|
|
126
|
+
default:
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function check(a, b, check) {
|
|
131
|
+
return [a, b, check];
|
|
132
|
+
}
|
|
133
|
+
function createChecksMap(checks) {
|
|
134
|
+
const map = new Map();
|
|
135
|
+
for (const [a, b, check] of checks) {
|
|
136
|
+
const fn = (target) => {
|
|
137
|
+
if (!check(target)) {
|
|
138
|
+
throw new Error(`Schema keys '${a}' and '${b}' are conflicting (${a}: ${JSON.stringify(target[a])}, ${b}: ${JSON.stringify(target[b])})`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
for (const k of [
|
|
142
|
+
[a, b],
|
|
143
|
+
[b, a],
|
|
144
|
+
]) {
|
|
145
|
+
let arr = map.get(k[0]);
|
|
146
|
+
if (arr === undefined) {
|
|
147
|
+
arr = [];
|
|
148
|
+
map.set(k[0], arr);
|
|
149
|
+
}
|
|
150
|
+
arr.push({ oppositeKey: k[1], check: fn });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return map;
|
|
154
|
+
}
|
|
155
|
+
export const DEFAULT_CHECKS = [
|
|
156
|
+
check("minimum", "maximum", (t) => t.maximum >= t.minimum),
|
|
157
|
+
check("exclusiveMinimum", "maximum", (t) => t.maximum > t.exclusiveMinimum),
|
|
158
|
+
check("minimum", "exclusiveMaximum", (t) => t.exclusiveMaximum > t.minimum),
|
|
159
|
+
check("exclusiveMinimum", "exclusiveMaximum", (t) => t.exclusiveMaximum > t.exclusiveMinimum),
|
|
160
|
+
check("minLength", "maxLength", (t) => t.maxLength >= t.minLength),
|
|
161
|
+
check("minItems", "maxItems", (t) => t.maxItems >= t.minItems),
|
|
162
|
+
check("minProperties", "maxProperties", (t) => t.maxProperties >= t.minProperties),
|
|
163
|
+
];
|
|
164
|
+
export function createMerger({ mergePatterns = simplePatternsMerger, isSubRegExp = Object.is, intersectJson = intersection, deduplicateJsonSchemaDef = identity, defaultMerger = identity, assigners = [], checks = DEFAULT_CHECKS, mergers, } = {}) {
|
|
165
|
+
function mergeArrayOfSchemaDefinitions(schemas) {
|
|
166
|
+
const l = schemas.length;
|
|
167
|
+
let result = schemas[0];
|
|
168
|
+
for (let i = 1; i < l; i++) {
|
|
169
|
+
const r = mergeSchemaDefinitions(result, schemas[i]);
|
|
170
|
+
if (r === false) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (isAllowAnySchema(r)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
result = r;
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
function createProperty(constraints, key, value, patterns, oppositeValue, oppositePatterns, oppositeAdditional) {
|
|
181
|
+
constraints.length = 0;
|
|
182
|
+
if (value === false) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
constraints.push(value);
|
|
186
|
+
const isOppositeValueDefined = oppositeValue !== undefined;
|
|
187
|
+
if (isOppositeValueDefined) {
|
|
188
|
+
if (oppositeValue === false) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
constraints.push(oppositeValue);
|
|
192
|
+
}
|
|
193
|
+
if (appendKeyConstraints(constraints, key, oppositePatterns)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const isNotYetAllowed = constraints.length < 2;
|
|
197
|
+
if (oppositeAdditional === false) {
|
|
198
|
+
// There are no allowing constraints from opposite side -> drop property
|
|
199
|
+
if (isNotYetAllowed) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
// Applying patterns of current schema cause they may disappear
|
|
203
|
+
if (appendKeyConstraints(constraints, key, patterns)) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (isNotYetAllowed && oppositeAdditional !== undefined) {
|
|
208
|
+
constraints.push(oppositeAdditional);
|
|
209
|
+
}
|
|
210
|
+
const l = constraints.length;
|
|
211
|
+
if (l === 1) {
|
|
212
|
+
return constraints[0];
|
|
213
|
+
}
|
|
214
|
+
return mergeArrayOfSchemaDefinitions(constraints);
|
|
215
|
+
}
|
|
216
|
+
function assignPatternPropertiesAndAdditionalPropertiesMerge(target, patterns, patternKeys, matchedPatterns, oppositeAdditional, isOppositeTruthy) {
|
|
217
|
+
const l = patternKeys.length;
|
|
218
|
+
if (l > 0 && oppositeAdditional !== false) {
|
|
219
|
+
if (isOppositeTruthy) {
|
|
220
|
+
// TODO: in some cases we can just assign new value instead of copying
|
|
221
|
+
Object.assign(target, patterns);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
for (let i = 0; i < l; i++) {
|
|
225
|
+
const pattern = patternKeys[i];
|
|
226
|
+
if (matchedPatterns.has(pattern)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
target[pattern] = mergeSchemaDefinitions(patterns[pattern], oppositeAdditional);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return target;
|
|
234
|
+
}
|
|
235
|
+
const propertiesAssigner = (target, { properties: lProps = {}, patternProperties: lPatterns, additionalProperties: lAdditional = true, }, { properties: rProps = {}, patternProperties: rPatterns, additionalProperties: rAdditional = true, }) => {
|
|
236
|
+
// Special case
|
|
237
|
+
const isLAddTruthy = isAllowAnySchema(lAdditional);
|
|
238
|
+
const isRAddTruthy = isAllowAnySchema(rAdditional);
|
|
239
|
+
if (isLAddTruthy && isRAddTruthy) {
|
|
240
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "properties", mergeRecordsOfSchemaDefinitions(lProps, rProps));
|
|
241
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "patternProperties", lPatterns && rPatterns
|
|
242
|
+
? mergeRecordsOfSchemaDefinitions(lPatterns, rPatterns)
|
|
243
|
+
: (lPatterns ?? rPatterns));
|
|
244
|
+
delete target.additionalProperties;
|
|
245
|
+
return target;
|
|
246
|
+
}
|
|
247
|
+
// Additional Properties
|
|
248
|
+
const additionalProperties = mergeSchemaDefinitions(lAdditional, rAdditional);
|
|
249
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "additionalProperties", additionalProperties);
|
|
250
|
+
// Properties
|
|
251
|
+
const properties = {};
|
|
252
|
+
const lKeys = Object.keys(lProps);
|
|
253
|
+
const lKeysLen = lKeys.length;
|
|
254
|
+
const [lCompiledPatterns, lPatternKeys] = lPatterns
|
|
255
|
+
? compilePatterns(lPatterns)
|
|
256
|
+
: EMPTY_PATTERNS_AND_KEYS;
|
|
257
|
+
const [rCompiledPatterns, rPatternKeys] = rPatterns
|
|
258
|
+
? compilePatterns(rPatterns)
|
|
259
|
+
: EMPTY_PATTERNS_AND_KEYS;
|
|
260
|
+
const constraints = [];
|
|
261
|
+
const lKeysSet = new Set();
|
|
262
|
+
const mappedRAdditional = isRAddTruthy ? undefined : rAdditional;
|
|
263
|
+
for (let i = 0; i < lKeysLen; i++) {
|
|
264
|
+
const key = lKeys[i];
|
|
265
|
+
lKeysSet.add(key);
|
|
266
|
+
const prop = createProperty(constraints, key, lProps[key], lCompiledPatterns, rProps[key], rCompiledPatterns, mappedRAdditional);
|
|
267
|
+
if (prop !== undefined) {
|
|
268
|
+
properties[key] = prop;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const rKeys = Object.keys(rProps);
|
|
272
|
+
const rKeysLen = rKeys.length;
|
|
273
|
+
const mappedLAdditional = isLAddTruthy ? undefined : lAdditional;
|
|
274
|
+
for (let i = 0; i < rKeysLen; i++) {
|
|
275
|
+
const key = rKeys[i];
|
|
276
|
+
if (lKeysSet.has(key)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const prop = createProperty(constraints, key, rProps[key], rCompiledPatterns, undefined, lCompiledPatterns, mappedLAdditional);
|
|
280
|
+
if (prop !== undefined) {
|
|
281
|
+
properties[key] = prop;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "properties", properties);
|
|
285
|
+
// Pattern Properties
|
|
286
|
+
// (lPatterns and rPatterns) or (lPatterns and rAdditional) or (rPatterns and lAdditional)
|
|
287
|
+
let patterns = {};
|
|
288
|
+
const matchedPatterns = new Set();
|
|
289
|
+
if (lPatternKeys.length > 0 && rPatternKeys.length > 0) {
|
|
290
|
+
const gen = createPairCombinations(lPatternKeys, rPatternKeys, (lKey, rKey) => {
|
|
291
|
+
if (isSubRegExp(lKey, rKey)) {
|
|
292
|
+
matchedPatterns.add(lKey);
|
|
293
|
+
}
|
|
294
|
+
if (isSubRegExp(rKey, lKey)) {
|
|
295
|
+
matchedPatterns.add(rKey);
|
|
296
|
+
}
|
|
297
|
+
patterns[mergePatterns(lKey, rKey)] = mergeSchemaDefinitions(lPatterns[lKey], rPatterns[rKey]);
|
|
298
|
+
});
|
|
299
|
+
while (!gen.next().done) {
|
|
300
|
+
/* empty */
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
patterns = assignPatternPropertiesAndAdditionalPropertiesMerge(patterns, lPatterns, lPatternKeys, matchedPatterns, rAdditional, isRAddTruthy);
|
|
304
|
+
patterns = assignPatternPropertiesAndAdditionalPropertiesMerge(patterns, rPatterns, rPatternKeys, matchedPatterns, lAdditional, isLAddTruthy);
|
|
305
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "patternProperties", patterns);
|
|
306
|
+
return target;
|
|
307
|
+
};
|
|
308
|
+
const itemsAssigner = (target,
|
|
309
|
+
// NOTE: Schema that has `additionalItems` without an `items` keyword is invalid
|
|
310
|
+
// so the assigner should be triggered only be colliding `items` properties
|
|
311
|
+
// so default values are used only for type narrowing
|
|
312
|
+
{ items: lItems = [], additionalItems: lAdditional }, { items: rItems = [], additionalItems: rAdditional }) => {
|
|
313
|
+
const isLArr = Array.isArray(lItems);
|
|
314
|
+
const isRArr = Array.isArray(rItems);
|
|
315
|
+
const itemsArray = [];
|
|
316
|
+
target.items = itemsArray;
|
|
317
|
+
if (isLArr && isRArr) {
|
|
318
|
+
const [l, additional, tail] = lItems.length < rItems.length
|
|
319
|
+
? [lItems.length, lAdditional, rItems]
|
|
320
|
+
: [rItems.length, rAdditional, lItems];
|
|
321
|
+
let i = 0;
|
|
322
|
+
for (; i < l; i++) {
|
|
323
|
+
itemsArray.push(mergeSchemaDefinitions(lItems[i], rItems[i]));
|
|
324
|
+
}
|
|
325
|
+
if (additional === false) {
|
|
326
|
+
target.additionalItems = false;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const isAdditionalTruthy = additional === undefined || isAllowAnySchema(additional);
|
|
330
|
+
for (; i < tail.length; i++) {
|
|
331
|
+
itemsArray.push(isAdditionalTruthy
|
|
332
|
+
? tail[i]
|
|
333
|
+
: mergeSchemaDefinitions(tail[i], additional));
|
|
334
|
+
}
|
|
335
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "additionalItems", lAdditional !== undefined && rAdditional !== undefined
|
|
336
|
+
? mergeSchemaDefinitions(lAdditional, rAdditional)
|
|
337
|
+
: (lAdditional ?? rAdditional));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else if (isLArr || isRArr) {
|
|
341
|
+
const [arr, item, additional] = (isLArr ? [lItems, rItems, lAdditional] : [rItems, lItems, rAdditional]);
|
|
342
|
+
assignSchemaDefinitionOrRecordOfSchemaDefinitions(target, "additionalItems", additional && mergeSchemaDefinitions(additional, item));
|
|
343
|
+
for (let i = 0; i < arr.length; i++) {
|
|
344
|
+
itemsArray.push(mergeSchemaDefinitions(arr[i], item));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
delete target.additionalItems;
|
|
349
|
+
target.items = mergeSchemaDefinitions(lItems, rItems);
|
|
350
|
+
}
|
|
351
|
+
return target;
|
|
352
|
+
};
|
|
353
|
+
const conditionAssigner = (target, l, r) => {
|
|
354
|
+
assignCondition(target, l);
|
|
355
|
+
const cond = assignCondition({}, r);
|
|
356
|
+
if (target.allOf === undefined) {
|
|
357
|
+
target.allOf = [cond];
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
target.allOf = target.allOf.concat(cond);
|
|
361
|
+
}
|
|
362
|
+
return target;
|
|
363
|
+
};
|
|
364
|
+
function mergeArraysOfSchemaDefinition(l, r) {
|
|
365
|
+
return deduplicateJsonSchemaDef(Array.from(createPairCombinations(l, r, mergeSchemaDefinitions)));
|
|
366
|
+
}
|
|
367
|
+
const ASSIGNERS_MAP = createMap([
|
|
368
|
+
[PROPERTIES_ASSIGNER_KEYS, propertiesAssigner],
|
|
369
|
+
[ITEMS_ASSIGNER_KEYS, itemsAssigner],
|
|
370
|
+
[CONDITION_ASSIGNER_KEYS, conditionAssigner],
|
|
371
|
+
...assigners,
|
|
372
|
+
]);
|
|
373
|
+
const CHECKS_MAP = createChecksMap(checks);
|
|
374
|
+
function mergeSchemaDefinitions(left, right) {
|
|
375
|
+
if (left === false || right === false) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
if (isAllowAnySchema(left)) {
|
|
379
|
+
if (isAllowAnySchema(right)) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
return right;
|
|
383
|
+
}
|
|
384
|
+
if (isAllowAnySchema(right)) {
|
|
385
|
+
return left;
|
|
386
|
+
}
|
|
387
|
+
let target = { ...left };
|
|
388
|
+
const assigners = new Set();
|
|
389
|
+
const checks = new Set();
|
|
390
|
+
const rKeys = Object.keys(right);
|
|
391
|
+
const l = rKeys.length;
|
|
392
|
+
for (let i = 0; i < l; i++) {
|
|
393
|
+
const rKey = rKeys[i];
|
|
394
|
+
const rv = right[rKey];
|
|
395
|
+
if (rv === undefined) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const checkData = CHECKS_MAP.get(rKey);
|
|
399
|
+
if (checkData !== undefined) {
|
|
400
|
+
const l = checkData.length;
|
|
401
|
+
for (let j = 0; j < l; j++) {
|
|
402
|
+
const item = checkData[j];
|
|
403
|
+
if (left[item.oppositeKey] !== undefined) {
|
|
404
|
+
checks.add(item.check);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const lv = left[rKey];
|
|
409
|
+
if (lv === undefined) {
|
|
410
|
+
// @ts-expect-error too complex
|
|
411
|
+
target[rKey] = rv;
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const assign = ASSIGNERS_MAP.get(rKey);
|
|
415
|
+
if (assign) {
|
|
416
|
+
assigners.add(assign);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const merge = MERGERS[rKey] ?? defaultMerger;
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
421
|
+
target[rKey] = merge(lv, rv);
|
|
422
|
+
}
|
|
423
|
+
for (const assign of assigners) {
|
|
424
|
+
target = assign(target, left, right);
|
|
425
|
+
}
|
|
426
|
+
for (const check of checks) {
|
|
427
|
+
check(target);
|
|
428
|
+
}
|
|
429
|
+
return target;
|
|
430
|
+
}
|
|
431
|
+
const mergeRecordsOfSchemaDefinitions = createRecordsMerge(mergeSchemaDefinitions);
|
|
432
|
+
const MERGERS = {
|
|
433
|
+
$id: defaultMerger,
|
|
434
|
+
$ref: defaultMerger,
|
|
435
|
+
$schema: defaultMerger,
|
|
436
|
+
$comment: defaultMerger,
|
|
437
|
+
$defs: mergeRecordsOfSchemaDefinitions,
|
|
438
|
+
definitions: mergeRecordsOfSchemaDefinitions,
|
|
439
|
+
type: (a, b) => {
|
|
440
|
+
if (a === b) {
|
|
441
|
+
return a;
|
|
442
|
+
}
|
|
443
|
+
const isAArr = Array.isArray(a);
|
|
444
|
+
const isBArr = Array.isArray(b);
|
|
445
|
+
if (!isAArr && !isBArr) {
|
|
446
|
+
const intersection = intersectSchemaTypes(a, b);
|
|
447
|
+
if (intersection !== undefined) {
|
|
448
|
+
return intersection;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else if (isAArr || isBArr) {
|
|
452
|
+
const r = new Set();
|
|
453
|
+
if (isAArr && isBArr) {
|
|
454
|
+
for (const intersection of createPairCombinations(a, b, intersectSchemaTypes)) {
|
|
455
|
+
if (intersection !== undefined) {
|
|
456
|
+
r.add(intersection);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
const arr = (isAArr ? a : b);
|
|
462
|
+
const el = (isAArr ? b : a);
|
|
463
|
+
const l = arr.length;
|
|
464
|
+
for (let i = 0; i < l; i++) {
|
|
465
|
+
const intersection = intersectSchemaTypes(el, arr[i]);
|
|
466
|
+
if (intersection !== undefined) {
|
|
467
|
+
r.add(intersection);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const s = r.size;
|
|
472
|
+
if (s === 1) {
|
|
473
|
+
return r.values().next().value;
|
|
474
|
+
}
|
|
475
|
+
if (s > 1) {
|
|
476
|
+
return Array.from(r);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
throw new Error(`It is not possible to create an intersection of the following incompatible types: ${a.toString()}, ${b.toString()}`);
|
|
480
|
+
},
|
|
481
|
+
default: defaultMerger,
|
|
482
|
+
description: defaultMerger,
|
|
483
|
+
title: defaultMerger,
|
|
484
|
+
const: defaultMerger,
|
|
485
|
+
format: defaultMerger,
|
|
486
|
+
contentEncoding: defaultMerger,
|
|
487
|
+
contentMediaType: defaultMerger,
|
|
488
|
+
not: (a, b) => {
|
|
489
|
+
const items = deduplicateJsonSchemaDef([a, b]);
|
|
490
|
+
return items.length === 1 ? items[0] : { anyOf: items };
|
|
491
|
+
},
|
|
492
|
+
pattern: mergePatterns,
|
|
493
|
+
readOnly: mergeBooleans,
|
|
494
|
+
writeOnly: mergeBooleans,
|
|
495
|
+
enum: (a, b) => {
|
|
496
|
+
const data = intersectJson(a, b);
|
|
497
|
+
if (data.length === 0) {
|
|
498
|
+
throw new Error(`Intersection of the following enums is empty: "${JSON.stringify(a)}", "${JSON.stringify(b)}"`);
|
|
499
|
+
}
|
|
500
|
+
return data;
|
|
501
|
+
},
|
|
502
|
+
anyOf: mergeArraysOfSchemaDefinition,
|
|
503
|
+
oneOf: mergeArraysOfSchemaDefinition,
|
|
504
|
+
allOf: (l, r) => deduplicateJsonSchemaDef(l.concat(r)),
|
|
505
|
+
propertyNames: mergeSchemaDefinitions,
|
|
506
|
+
contains: mergeSchemaDefinitions,
|
|
507
|
+
dependencies: createRecordsMerge((a, b) => {
|
|
508
|
+
if (Array.isArray(a)) {
|
|
509
|
+
if (Array.isArray(b)) {
|
|
510
|
+
return union(a, b);
|
|
511
|
+
}
|
|
512
|
+
return mergeSchemaDefinitions(b, { required: a });
|
|
513
|
+
}
|
|
514
|
+
if (Array.isArray(b)) {
|
|
515
|
+
return mergeSchemaDefinitions(a, { required: b });
|
|
516
|
+
}
|
|
517
|
+
return mergeSchemaDefinitions(a, b);
|
|
518
|
+
}),
|
|
519
|
+
examples: (l, r) => {
|
|
520
|
+
// https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-10.4
|
|
521
|
+
if (!Array.isArray(l) || !Array.isArray(r)) {
|
|
522
|
+
throw new Error(`Value of the 'examples' field should be an array, but got "${JSON.stringify(l)}" and "${JSON.stringify(r)}"`);
|
|
523
|
+
}
|
|
524
|
+
// TODO: Proper deduplication
|
|
525
|
+
return union(l, r);
|
|
526
|
+
},
|
|
527
|
+
multipleOf: (a, b) => {
|
|
528
|
+
let factor = 1;
|
|
529
|
+
while (!Number.isInteger(a) || !Number.isInteger(b)) {
|
|
530
|
+
factor *= 10;
|
|
531
|
+
a *= 10;
|
|
532
|
+
b *= 10;
|
|
533
|
+
}
|
|
534
|
+
return lcm(a, b) / factor;
|
|
535
|
+
},
|
|
536
|
+
exclusiveMaximum: Math.min,
|
|
537
|
+
maximum: Math.min,
|
|
538
|
+
maxItems: Math.min,
|
|
539
|
+
maxLength: Math.min,
|
|
540
|
+
maxProperties: Math.min,
|
|
541
|
+
exclusiveMinimum: Math.max,
|
|
542
|
+
minimum: Math.max,
|
|
543
|
+
minItems: Math.max,
|
|
544
|
+
minLength: Math.max,
|
|
545
|
+
minProperties: Math.max,
|
|
546
|
+
uniqueItems: mergeBooleans,
|
|
547
|
+
required: union,
|
|
548
|
+
...mergers,
|
|
549
|
+
};
|
|
550
|
+
return {
|
|
551
|
+
mergeSchemaDefinitions,
|
|
552
|
+
mergeArrayOfSchemaDefinitions,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { JSONSchema7 as Schema, JSONSchema7Definition as SchemaDefinition } from "json-schema";
|
|
2
|
+
import { type AnySubSchemaKey, type TransformedSchemaDefinition } from "./json-schema.ts";
|
|
3
|
+
import type { SchemaTraverserContext } from "./traverse.ts";
|
|
4
|
+
export declare function transformSchemaDefinition<R>(schema: SchemaDefinition, transform: (shallowCopy: TransformedSchemaDefinition<R, Schema>, ctx: SchemaTraverserContext<AnySubSchemaKey>) => R, ctx?: SchemaTraverserContext<AnySubSchemaKey>): R;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SUB_SCHEMAS, RECORDS_OF_SUB_SCHEMAS, ARRAYS_OF_SUB_SCHEMAS, isSchemaObject, } from "./json-schema.js";
|
|
2
|
+
export function transformSchemaDefinition(schema, transform, ctx = { type: "root", path: [] }) {
|
|
3
|
+
if (!isSchemaObject(schema)) {
|
|
4
|
+
return transform(schema, ctx);
|
|
5
|
+
}
|
|
6
|
+
const shallowCopy = {
|
|
7
|
+
...schema,
|
|
8
|
+
};
|
|
9
|
+
for (const key of ARRAYS_OF_SUB_SCHEMAS) {
|
|
10
|
+
const array = schema[key];
|
|
11
|
+
if (array === undefined || !Array.isArray(array)) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const c = {
|
|
15
|
+
type: "array",
|
|
16
|
+
parent: schema,
|
|
17
|
+
key,
|
|
18
|
+
index: 0,
|
|
19
|
+
path: ctx.path.concat(key, 0),
|
|
20
|
+
};
|
|
21
|
+
shallowCopy[key] = array.map((item, index) => {
|
|
22
|
+
c.index = index;
|
|
23
|
+
c.path[c.path.length - 1] = index;
|
|
24
|
+
return transformSchemaDefinition(item, transform, c);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const map = new Map();
|
|
28
|
+
for (const key of RECORDS_OF_SUB_SCHEMAS) {
|
|
29
|
+
const record = schema[key];
|
|
30
|
+
if (record === undefined) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const c = {
|
|
34
|
+
type: "record",
|
|
35
|
+
parent: schema,
|
|
36
|
+
key,
|
|
37
|
+
property: "",
|
|
38
|
+
path: ctx.path.concat(key, ""),
|
|
39
|
+
};
|
|
40
|
+
const keys = Object.keys(record);
|
|
41
|
+
const keysLen = keys.length;
|
|
42
|
+
for (let i = 0; i < keysLen; i++) {
|
|
43
|
+
const property = keys[i];
|
|
44
|
+
const value = record[property];
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
map.set(property, value);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
c.property = property;
|
|
50
|
+
c.path[c.path.length - 1] = property;
|
|
51
|
+
map.set(property, transformSchemaDefinition(value, transform, c));
|
|
52
|
+
}
|
|
53
|
+
shallowCopy[key] = Object.fromEntries(map);
|
|
54
|
+
map.clear();
|
|
55
|
+
}
|
|
56
|
+
const c = {
|
|
57
|
+
type: "sub",
|
|
58
|
+
parent: schema,
|
|
59
|
+
key: "items",
|
|
60
|
+
path: ctx.path.concat(""),
|
|
61
|
+
};
|
|
62
|
+
for (const key of SUB_SCHEMAS) {
|
|
63
|
+
const value = schema[key];
|
|
64
|
+
if (value === undefined || Array.isArray(value)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
c.key = key;
|
|
68
|
+
c.path[c.path.length - 1] = key;
|
|
69
|
+
shallowCopy[key] = transformSchemaDefinition(value, transform, c);
|
|
70
|
+
}
|
|
71
|
+
return transform(shallowCopy, ctx);
|
|
72
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { JSONSchema7 as Schema, JSONSchema7Definition as SchemaDefinition } from "json-schema";
|
|
2
|
+
import type { Visitor } from "../traverser.ts";
|
|
3
|
+
import type { AnySubSchemaKey, SubSchemaKey, SubSchemasArrayKey, SubSchemasRecordKey } from "./json-schema.ts";
|
|
4
|
+
export type SchemaTraverserContextType = "array" | "record" | "sub" | "root";
|
|
5
|
+
export interface AbstractSchemaTraverserContext<T extends SchemaTraverserContextType, K extends AnySubSchemaKey> {
|
|
6
|
+
type: T;
|
|
7
|
+
path: SubSchemasArrayKey extends K ? Array<string | number> : string[];
|
|
8
|
+
}
|
|
9
|
+
export interface ArraySchemaTraverserContext<K extends AnySubSchemaKey> extends AbstractSchemaTraverserContext<"array", K> {
|
|
10
|
+
parent: Schema;
|
|
11
|
+
key: SubSchemasArrayKey & K;
|
|
12
|
+
index: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RecordSchemaTraverserContext<K extends AnySubSchemaKey> extends AbstractSchemaTraverserContext<"record", K> {
|
|
15
|
+
parent: Schema;
|
|
16
|
+
key: SubSchemasRecordKey & K;
|
|
17
|
+
property: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SubSchemaTraverserContext<K extends AnySubSchemaKey> extends AbstractSchemaTraverserContext<"sub", K> {
|
|
20
|
+
parent: Schema;
|
|
21
|
+
key: SubSchemaKey & K;
|
|
22
|
+
}
|
|
23
|
+
export interface RootSchemaTraverserContext<K extends AnySubSchemaKey> extends AbstractSchemaTraverserContext<"root", K> {
|
|
24
|
+
}
|
|
25
|
+
export type SchemaTraverserContext<K extends AnySubSchemaKey> = ArraySchemaTraverserContext<K> | RecordSchemaTraverserContext<K> | SubSchemaTraverserContext<K> | RootSchemaTraverserContext<K>;
|
|
26
|
+
export type SchemaDefinitionVisitor<K extends AnySubSchemaKey, R> = Visitor<SchemaDefinition, SchemaTraverserContext<K>, R>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/lib/math.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface MapLike<K, V> {
|
|
2
|
+
has(key: K): boolean;
|
|
3
|
+
get(key: K): V | undefined;
|
|
4
|
+
set(key: K, value: V): void;
|
|
5
|
+
}
|
|
6
|
+
export declare function memoize<Arg, Return>(cache: MapLike<Arg, Return>, func: (arg: Arg) => Return): (arg: Arg) => Return;
|
|
7
|
+
export declare const weakMemoize: <Arg extends object, Return>(cache: WeakMap<Arg, Return>, fn: (arg: Arg) => Return) => (arg: Arg) => Return;
|