@x0k/json-schema-merge 1.0.4 → 1.0.5
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/package.json +7 -2
- package/src/index.ts +1 -0
- package/src/lib/array.ts +172 -0
- package/src/lib/function.ts +3 -0
- package/src/lib/json-schema/compare/compare.ts +343 -0
- package/src/lib/json-schema/compare/index.ts +1 -0
- package/src/lib/json-schema/index.ts +5 -0
- package/src/lib/json-schema/json-schema.ts +113 -0
- package/src/lib/json-schema/merge/all-of-merge.ts +44 -0
- package/src/lib/json-schema/merge/index.ts +3 -0
- package/src/lib/json-schema/merge/merge.ts +880 -0
- package/src/lib/json-schema/merge/patterns.ts +7 -0
- package/src/lib/json-schema/transform.ts +99 -0
- package/src/lib/json-schema/traverse.ts +61 -0
- package/src/lib/math.ts +3 -0
- package/src/lib/memoize.ts +24 -0
- package/src/lib/object.ts +16 -0
- package/src/lib/ord.ts +16 -0
- package/src/lib/traverser.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x0k/json-schema-merge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A minimal JSON Schema merging library.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,8 +13,13 @@
|
|
|
13
13
|
"url": "git+https://github.com/x0k/json-schema-merge.git"
|
|
14
14
|
},
|
|
15
15
|
"author": "Roman Krasilnikov",
|
|
16
|
+
"sideEffects": false,
|
|
16
17
|
"files": [
|
|
17
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"src",
|
|
20
|
+
"!src/**/*.test.ts",
|
|
21
|
+
"!src/**/*.bench.ts",
|
|
22
|
+
"!src/**/fixtures"
|
|
18
23
|
],
|
|
19
24
|
"module": "./dist/index.js",
|
|
20
25
|
"types": "./dist/index.d.ts",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lib/json-schema/index.ts";
|
package/src/lib/array.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Comparator } from "./ord.ts";
|
|
2
|
+
|
|
3
|
+
export function union<T>(larger: T[], smaller: T[]): T[] {
|
|
4
|
+
const ll = larger.length;
|
|
5
|
+
if (ll === 0) {
|
|
6
|
+
return smaller;
|
|
7
|
+
}
|
|
8
|
+
let sl = smaller.length;
|
|
9
|
+
if (sl === 0) {
|
|
10
|
+
return larger;
|
|
11
|
+
}
|
|
12
|
+
if (ll < sl) {
|
|
13
|
+
const tmp = larger;
|
|
14
|
+
larger = smaller;
|
|
15
|
+
smaller = tmp;
|
|
16
|
+
sl = ll;
|
|
17
|
+
}
|
|
18
|
+
const data = new Set(larger);
|
|
19
|
+
for (let i = 0; i < sl; i++) {
|
|
20
|
+
data.add(smaller[i]!);
|
|
21
|
+
}
|
|
22
|
+
return Array.from(data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function intersection<T>(a: T[], b: T[]): T[] {
|
|
26
|
+
const result: T[] = [];
|
|
27
|
+
|
|
28
|
+
if (a.length === 0 || b.length === 0) {
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (a.length > b.length) {
|
|
33
|
+
const tmp = a;
|
|
34
|
+
a = b;
|
|
35
|
+
b = tmp;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const setB = new Set(b);
|
|
39
|
+
for (let i = 0; i < a.length && setB.size > 0; i++) {
|
|
40
|
+
const val = a[i]!;
|
|
41
|
+
if (setB.delete(val)) {
|
|
42
|
+
result.push(val);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isArrayEmpty<T>(arr: T[]): arr is [] {
|
|
50
|
+
return arr.length === 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createArrayComparator<T>(compare: (a: T, b: T) => number) {
|
|
54
|
+
return (a: T[], b: T[]) => {
|
|
55
|
+
const d = a.length - b.length;
|
|
56
|
+
if (d !== 0) {
|
|
57
|
+
return d;
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < a.length; i++) {
|
|
60
|
+
if (a[i] !== b[i]) {
|
|
61
|
+
const d = compare(a[i]!, b[i]!);
|
|
62
|
+
if (d !== 0) {
|
|
63
|
+
return d;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Function type for removing duplicates from an array.
|
|
73
|
+
*/
|
|
74
|
+
export type Deduplicator<T> = (data: T[]) => T[];
|
|
75
|
+
|
|
76
|
+
export interface DeduplicatorOptions {
|
|
77
|
+
/** @default 12 */
|
|
78
|
+
threshold?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createDeduplicator<T>(
|
|
82
|
+
compare: Comparator<T>,
|
|
83
|
+
{ threshold = 12 }: DeduplicatorOptions = {}
|
|
84
|
+
): Deduplicator<T> {
|
|
85
|
+
return (arr) => {
|
|
86
|
+
const al = arr.length;
|
|
87
|
+
|
|
88
|
+
if (al === 0) {
|
|
89
|
+
return arr;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (al <= threshold) {
|
|
93
|
+
const result: T[] = [];
|
|
94
|
+
let rl = 0;
|
|
95
|
+
outer: for (let i = 0; i < al; i++) {
|
|
96
|
+
const item = arr[i]!;
|
|
97
|
+
for (let j = 0; j < rl; j++) {
|
|
98
|
+
if (compare(item, result[j]!) === 0) {
|
|
99
|
+
continue outer;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
rl = result.push(item);
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sorted = arr.slice().sort(compare);
|
|
108
|
+
let wIndex = 0;
|
|
109
|
+
|
|
110
|
+
for (let rIndex = 1; rIndex < al; rIndex++) {
|
|
111
|
+
if (compare(sorted[wIndex]!, sorted[rIndex]!) !== 0) {
|
|
112
|
+
if (++wIndex !== rIndex) {
|
|
113
|
+
sorted[wIndex] = sorted[rIndex]!;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
sorted.length = wIndex + 1;
|
|
118
|
+
return sorted;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Function type for intersecting two arrays of the same type.
|
|
124
|
+
*/
|
|
125
|
+
export type Intersector<T> = (a: T[], b: T[]) => T[];
|
|
126
|
+
|
|
127
|
+
export function createIntersector<T>(compare: Comparator<T>): Intersector<T> {
|
|
128
|
+
return (a, b) => {
|
|
129
|
+
const result: T[] = [];
|
|
130
|
+
let al = a.length;
|
|
131
|
+
let bl = b.length;
|
|
132
|
+
|
|
133
|
+
if (al === 0 || bl === 0) {
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (al > bl) {
|
|
138
|
+
const tmpArr = a;
|
|
139
|
+
a = b;
|
|
140
|
+
b = tmpArr;
|
|
141
|
+
const tmpL = al;
|
|
142
|
+
al = bl;
|
|
143
|
+
bl = tmpL;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const aSorted = [...a].sort(compare);
|
|
147
|
+
const bSorted = [...b].sort(compare);
|
|
148
|
+
|
|
149
|
+
let i = 0,
|
|
150
|
+
j = 0;
|
|
151
|
+
while (i < al && j < bl) {
|
|
152
|
+
const cmp = compare(aSorted[i]!, bSorted[j]!);
|
|
153
|
+
if (cmp === 0) {
|
|
154
|
+
// Only push if result is empty OR last pushed value is different
|
|
155
|
+
if (
|
|
156
|
+
result.length === 0 ||
|
|
157
|
+
compare(result[result.length - 1]!, aSorted[i]!) !== 0
|
|
158
|
+
) {
|
|
159
|
+
result.push(aSorted[i]!);
|
|
160
|
+
}
|
|
161
|
+
i++;
|
|
162
|
+
j++;
|
|
163
|
+
} else if (cmp < 0) {
|
|
164
|
+
i++;
|
|
165
|
+
} else {
|
|
166
|
+
j++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return result;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
JSONSchema7Type as SchemaValue,
|
|
3
|
+
JSONSchema7TypeName as SchemaType,
|
|
4
|
+
JSONSchema7Definition,
|
|
5
|
+
JSONSchema7,
|
|
6
|
+
JSONSchema7Object,
|
|
7
|
+
} from "json-schema";
|
|
8
|
+
|
|
9
|
+
import { type Comparator, ascComparator } from "../../ord.ts";
|
|
10
|
+
import {
|
|
11
|
+
createArrayComparator,
|
|
12
|
+
createDeduplicator,
|
|
13
|
+
isArrayEmpty,
|
|
14
|
+
} from "../../array.ts";
|
|
15
|
+
import { isRecordEmpty } from "../../object.ts";
|
|
16
|
+
import { weakMemoize } from "../../memoize.ts";
|
|
17
|
+
|
|
18
|
+
import { isAllowAnySchema, isSchemaObject } from "../json-schema.ts";
|
|
19
|
+
|
|
20
|
+
const zero = () => 0;
|
|
21
|
+
const isUndefined = <T>(v: T | undefined): v is undefined => v === undefined;
|
|
22
|
+
|
|
23
|
+
type SchemaPrimitiveTypeExceptNullType = Extract<
|
|
24
|
+
SchemaType,
|
|
25
|
+
"string" | "number" | "boolean"
|
|
26
|
+
>;
|
|
27
|
+
type SchemaPrimitiveTypeExceptNull = string | number | boolean;
|
|
28
|
+
|
|
29
|
+
const isSchemaPrimitiveExceptNull = (
|
|
30
|
+
value: Exclude<SchemaValue, null>
|
|
31
|
+
): value is SchemaPrimitiveTypeExceptNull => typeof value !== "object";
|
|
32
|
+
|
|
33
|
+
const PRIMITIVE_TYPE_ORDER: Record<
|
|
34
|
+
SchemaPrimitiveTypeExceptNullType,
|
|
35
|
+
0 | 1 | 2
|
|
36
|
+
> = {
|
|
37
|
+
boolean: 0,
|
|
38
|
+
number: 1,
|
|
39
|
+
string: 2,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function compareSchemaPrimitive(
|
|
43
|
+
a: SchemaPrimitiveTypeExceptNull,
|
|
44
|
+
b: SchemaPrimitiveTypeExceptNull
|
|
45
|
+
) {
|
|
46
|
+
const ta = typeof a as SchemaPrimitiveTypeExceptNullType;
|
|
47
|
+
const tb = typeof b as SchemaPrimitiveTypeExceptNullType;
|
|
48
|
+
return ta === tb
|
|
49
|
+
? ascComparator(a, b)
|
|
50
|
+
: PRIMITIVE_TYPE_ORDER[ta] - PRIMITIVE_TYPE_ORDER[tb];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function insertUniqueValues<K>(mutableTarget: K[], mutableSource: K[]): K[] {
|
|
54
|
+
const tl = mutableTarget.length;
|
|
55
|
+
if (tl === 0) return mutableSource;
|
|
56
|
+
const sl = mutableSource.length;
|
|
57
|
+
if (sl === 0) return mutableTarget;
|
|
58
|
+
if (sl > tl) {
|
|
59
|
+
const t = mutableTarget;
|
|
60
|
+
mutableTarget = mutableSource;
|
|
61
|
+
mutableSource = t;
|
|
62
|
+
}
|
|
63
|
+
const seen = new Set(mutableTarget);
|
|
64
|
+
const l = mutableSource.length;
|
|
65
|
+
for (let i = 0; i < l; i++) {
|
|
66
|
+
const key = mutableSource[i]!;
|
|
67
|
+
if (!seen.has(key)) {
|
|
68
|
+
mutableTarget.push(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return mutableTarget;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createCmpMatcher<T, E extends T>(
|
|
75
|
+
isEmpty: (v: T) => v is E,
|
|
76
|
+
compare: Comparator<Exclude<T, E>>,
|
|
77
|
+
compareEmpty: Comparator<E> = zero
|
|
78
|
+
) {
|
|
79
|
+
return (a: T, b: T) => {
|
|
80
|
+
if (isEmpty(a)) {
|
|
81
|
+
if (isEmpty(b)) {
|
|
82
|
+
return compareEmpty(a, b);
|
|
83
|
+
}
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
86
|
+
if (isEmpty(b)) {
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
return compare(a as Exclude<T, E>, b as Exclude<T, E>);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createOptionalComparator<T>(compare: Comparator<T>) {
|
|
94
|
+
return createCmpMatcher<T | undefined, undefined>(isUndefined, compare);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createNarrowingOptionalComparator<T, E extends T>(
|
|
98
|
+
isEmpty: (v: T) => v is E,
|
|
99
|
+
compare: Comparator<Exclude<T, E | undefined>>
|
|
100
|
+
) {
|
|
101
|
+
return createCmpMatcher<T | undefined, E | undefined>(
|
|
102
|
+
(v: T | undefined): v is undefined | E => v === undefined || isEmpty(v),
|
|
103
|
+
compare
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createArrayOrItemComparator<T, T1>(
|
|
108
|
+
compare: Comparator<T>,
|
|
109
|
+
compareArray: Comparator<T1[]>
|
|
110
|
+
) {
|
|
111
|
+
return createCmpMatcher<T | T1[], T1[]>(Array.isArray, compare, compareArray);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const compareOptionalSameTypeSchemaPrimitives =
|
|
115
|
+
createOptionalComparator(ascComparator);
|
|
116
|
+
|
|
117
|
+
const compareNumbersWithZeroDefault = createNarrowingOptionalComparator(
|
|
118
|
+
(v: number): v is 0 => v === 0,
|
|
119
|
+
(a, b) => a - b
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
export interface ComparatorOptions {
|
|
123
|
+
deduplicationCache?: WeakMap<any[], any[]>;
|
|
124
|
+
sortedKeysCache?: WeakMap<Record<string, any>, string[]>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function createComparator({
|
|
128
|
+
deduplicationCache = new WeakMap(),
|
|
129
|
+
sortedKeysCache = new WeakMap(),
|
|
130
|
+
}: ComparatorOptions = {}) {
|
|
131
|
+
const getSortedKeys = weakMemoize(sortedKeysCache, (obj) =>
|
|
132
|
+
Object.keys(obj).sort()
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
function createRecordsComparator<R extends Record<string, any>>(
|
|
136
|
+
compare: <K extends keyof R>(a: R[K], b: R[K]) => number
|
|
137
|
+
) {
|
|
138
|
+
return (a: R, b: R) => {
|
|
139
|
+
const aKeys = getSortedKeys(a);
|
|
140
|
+
const bKeys = getSortedKeys(b);
|
|
141
|
+
const l = Math.min(aKeys.length, bKeys.length);
|
|
142
|
+
for (let i = 0; i < l; i++) {
|
|
143
|
+
const cmp = ascComparator(aKeys[i]!, bKeys[i]!);
|
|
144
|
+
if (cmp !== 0) {
|
|
145
|
+
return cmp;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (aKeys.length !== bKeys.length) {
|
|
149
|
+
return aKeys.length - bKeys.length;
|
|
150
|
+
}
|
|
151
|
+
for (let i = 0; i < l; i++) {
|
|
152
|
+
const key = aKeys[i]!;
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
154
|
+
const cmp = compare(a[key], b[key]);
|
|
155
|
+
if (cmp !== 0) {
|
|
156
|
+
return cmp;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return 0;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createArrayComparatorWithDeduplication<T>(compare: Comparator<T>) {
|
|
164
|
+
const cmp = createArrayComparator(compare);
|
|
165
|
+
const deduplicate = weakMemoize(
|
|
166
|
+
deduplicationCache as WeakMap<T[], T[]>,
|
|
167
|
+
// NOTE: Always sort output
|
|
168
|
+
createDeduplicator(compare, { threshold: 0 })
|
|
169
|
+
);
|
|
170
|
+
return (a: T[], b: T[]) => cmp(deduplicate(a), deduplicate(b));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const compareArrayOfSameTypePrimitivesWithDeduplication =
|
|
174
|
+
createArrayComparatorWithDeduplication(ascComparator);
|
|
175
|
+
|
|
176
|
+
function compareSchemaDefinitions(
|
|
177
|
+
a: JSONSchema7Definition,
|
|
178
|
+
b: JSONSchema7Definition
|
|
179
|
+
) {
|
|
180
|
+
if (isSchemaObject(a)) {
|
|
181
|
+
if (isSchemaObject(b)) {
|
|
182
|
+
const aKeys = Object.keys(a) as (keyof JSONSchema7)[];
|
|
183
|
+
const bKeys = Object.keys(b) as (keyof JSONSchema7)[];
|
|
184
|
+
const allKeys = insertUniqueValues(aKeys, bKeys);
|
|
185
|
+
const l = allKeys.length;
|
|
186
|
+
for (let i = 0; i < l; i++) {
|
|
187
|
+
const key = allKeys[i]!;
|
|
188
|
+
if (a[key] === b[key]) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const cmp = COMPARATORS[key] ?? compareOptionalSchemaValues;
|
|
192
|
+
const d = cmp(a[key] as undefined, b[key] as undefined);
|
|
193
|
+
if (d !== 0) {
|
|
194
|
+
return d;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
return b === true && isRecordEmpty(a) ? 0 : 1;
|
|
200
|
+
}
|
|
201
|
+
if (isSchemaObject(b)) {
|
|
202
|
+
return a === true && isRecordEmpty(b) ? 0 : -1;
|
|
203
|
+
}
|
|
204
|
+
return ascComparator(a, b);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const compareOptionalSchemaValues =
|
|
208
|
+
createOptionalComparator(compareSchemaValues);
|
|
209
|
+
|
|
210
|
+
const compareNonNullSchemaValue = createCmpMatcher(
|
|
211
|
+
isSchemaPrimitiveExceptNull,
|
|
212
|
+
createArrayOrItemComparator(
|
|
213
|
+
createRecordsComparator<JSONSchema7Object>(compareOptionalSchemaValues),
|
|
214
|
+
createArrayComparator(compareSchemaValues)
|
|
215
|
+
),
|
|
216
|
+
compareSchemaPrimitive
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
function compareSchemaValues(a: SchemaValue, b: SchemaValue): number {
|
|
220
|
+
if (a === null) {
|
|
221
|
+
return -1;
|
|
222
|
+
}
|
|
223
|
+
if (b === null) {
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
return compareNonNullSchemaValue(a, b);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const compareOptionalSchemaDefinitions = createOptionalComparator(
|
|
230
|
+
compareSchemaDefinitions
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const compareRecordOfOptionalSchemasWithEmptyRecordDefault =
|
|
234
|
+
createNarrowingOptionalComparator(
|
|
235
|
+
isRecordEmpty,
|
|
236
|
+
createRecordsComparator<Record<string, JSONSchema7Definition>>(
|
|
237
|
+
compareOptionalSchemaDefinitions
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const compareOptionalArrayOfSchemasWithDeduplication =
|
|
242
|
+
createOptionalComparator(
|
|
243
|
+
createArrayComparatorWithDeduplication(compareSchemaDefinitions)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const compareSchemaDefinitionsWithEmptyDefinitionDefault =
|
|
247
|
+
createNarrowingOptionalComparator(
|
|
248
|
+
isAllowAnySchema,
|
|
249
|
+
compareSchemaDefinitions
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const COMPARATORS: {
|
|
253
|
+
[K in keyof JSONSchema7]-?: Comparator<JSONSchema7[K]>;
|
|
254
|
+
} = {
|
|
255
|
+
$id: compareOptionalSameTypeSchemaPrimitives,
|
|
256
|
+
$comment: compareOptionalSameTypeSchemaPrimitives,
|
|
257
|
+
$defs: compareRecordOfOptionalSchemasWithEmptyRecordDefault,
|
|
258
|
+
$ref: compareOptionalSameTypeSchemaPrimitives,
|
|
259
|
+
$schema: compareOptionalSameTypeSchemaPrimitives,
|
|
260
|
+
const: compareOptionalSchemaValues,
|
|
261
|
+
contains: compareOptionalSchemaDefinitions,
|
|
262
|
+
contentEncoding: compareOptionalSameTypeSchemaPrimitives,
|
|
263
|
+
contentMediaType: compareOptionalSameTypeSchemaPrimitives,
|
|
264
|
+
default: compareOptionalSchemaValues,
|
|
265
|
+
definitions: compareRecordOfOptionalSchemasWithEmptyRecordDefault,
|
|
266
|
+
description: compareOptionalSameTypeSchemaPrimitives,
|
|
267
|
+
else: compareOptionalSchemaDefinitions,
|
|
268
|
+
examples: compareOptionalSchemaValues,
|
|
269
|
+
exclusiveMaximum: compareOptionalSameTypeSchemaPrimitives,
|
|
270
|
+
exclusiveMinimum: compareOptionalSameTypeSchemaPrimitives,
|
|
271
|
+
format: compareOptionalSameTypeSchemaPrimitives,
|
|
272
|
+
if: compareOptionalSchemaDefinitions,
|
|
273
|
+
maximum: compareOptionalSameTypeSchemaPrimitives,
|
|
274
|
+
maxItems: compareOptionalSameTypeSchemaPrimitives,
|
|
275
|
+
maxLength: compareOptionalSameTypeSchemaPrimitives,
|
|
276
|
+
maxProperties: compareOptionalSameTypeSchemaPrimitives,
|
|
277
|
+
minimum: compareOptionalSameTypeSchemaPrimitives,
|
|
278
|
+
multipleOf: compareOptionalSameTypeSchemaPrimitives,
|
|
279
|
+
not: compareOptionalSchemaDefinitions,
|
|
280
|
+
pattern: compareOptionalSameTypeSchemaPrimitives,
|
|
281
|
+
propertyNames: compareOptionalSchemaDefinitions,
|
|
282
|
+
readOnly: compareOptionalSameTypeSchemaPrimitives,
|
|
283
|
+
then: compareOptionalSchemaDefinitions,
|
|
284
|
+
title: compareOptionalSameTypeSchemaPrimitives,
|
|
285
|
+
writeOnly: compareOptionalSameTypeSchemaPrimitives,
|
|
286
|
+
uniqueItems: createNarrowingOptionalComparator(
|
|
287
|
+
(v): v is false => v === false,
|
|
288
|
+
zero
|
|
289
|
+
),
|
|
290
|
+
minLength: compareNumbersWithZeroDefault,
|
|
291
|
+
minItems: compareNumbersWithZeroDefault,
|
|
292
|
+
minProperties: compareNumbersWithZeroDefault,
|
|
293
|
+
required: createNarrowingOptionalComparator(
|
|
294
|
+
isArrayEmpty,
|
|
295
|
+
compareArrayOfSameTypePrimitivesWithDeduplication
|
|
296
|
+
),
|
|
297
|
+
enum: createNarrowingOptionalComparator(
|
|
298
|
+
isArrayEmpty,
|
|
299
|
+
createArrayComparatorWithDeduplication(compareSchemaValues)
|
|
300
|
+
),
|
|
301
|
+
type: createOptionalComparator((a, b) => {
|
|
302
|
+
const isAArr = Array.isArray(a);
|
|
303
|
+
const isBArr = Array.isArray(b);
|
|
304
|
+
if (!isAArr && !isBArr) {
|
|
305
|
+
return ascComparator(a, b);
|
|
306
|
+
}
|
|
307
|
+
return compareArrayOfSameTypePrimitivesWithDeduplication(
|
|
308
|
+
isAArr ? a : [a],
|
|
309
|
+
isBArr ? b : [b]
|
|
310
|
+
);
|
|
311
|
+
}),
|
|
312
|
+
items: createNarrowingOptionalComparator(
|
|
313
|
+
(v): v is true | JSONSchema7 => !Array.isArray(v) && isAllowAnySchema(v),
|
|
314
|
+
createArrayOrItemComparator(
|
|
315
|
+
compareSchemaDefinitions,
|
|
316
|
+
createArrayComparator(compareSchemaDefinitions)
|
|
317
|
+
)
|
|
318
|
+
),
|
|
319
|
+
anyOf: compareOptionalArrayOfSchemasWithDeduplication,
|
|
320
|
+
allOf: compareOptionalArrayOfSchemasWithDeduplication,
|
|
321
|
+
oneOf: compareOptionalArrayOfSchemasWithDeduplication,
|
|
322
|
+
properties: compareRecordOfOptionalSchemasWithEmptyRecordDefault,
|
|
323
|
+
patternProperties: compareRecordOfOptionalSchemasWithEmptyRecordDefault,
|
|
324
|
+
additionalProperties: compareSchemaDefinitionsWithEmptyDefinitionDefault,
|
|
325
|
+
additionalItems: compareSchemaDefinitionsWithEmptyDefinitionDefault,
|
|
326
|
+
dependencies: createNarrowingOptionalComparator(
|
|
327
|
+
isRecordEmpty,
|
|
328
|
+
createRecordsComparator(
|
|
329
|
+
createOptionalComparator(
|
|
330
|
+
createArrayOrItemComparator(
|
|
331
|
+
compareSchemaDefinitions,
|
|
332
|
+
compareArrayOfSameTypePrimitivesWithDeduplication
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
compareSchemaValues,
|
|
341
|
+
compareSchemaDefinitions,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./compare.ts";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
JSONSchema7,
|
|
3
|
+
JSONSchema7TypeName,
|
|
4
|
+
JSONSchema7Definition,
|
|
5
|
+
} from "json-schema";
|
|
6
|
+
|
|
7
|
+
import { isRecordEmpty } from "../object.ts";
|
|
8
|
+
|
|
9
|
+
export const JSON_SCHEMA_TYPE_NAMES: string[] = [
|
|
10
|
+
"array",
|
|
11
|
+
"boolean",
|
|
12
|
+
"integer",
|
|
13
|
+
"null",
|
|
14
|
+
"number",
|
|
15
|
+
"object",
|
|
16
|
+
"string",
|
|
17
|
+
] as const satisfies JSONSchema7TypeName[];
|
|
18
|
+
|
|
19
|
+
export const SET_OF_JSON_SCHEMA_TYPE_NAMES = new Set(JSON_SCHEMA_TYPE_NAMES);
|
|
20
|
+
|
|
21
|
+
export function isJsonSchemaType(type: string): type is JSONSchema7TypeName {
|
|
22
|
+
return SET_OF_JSON_SCHEMA_TYPE_NAMES.has(type);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type SchemaKeys = Array<keyof JSONSchema7>;
|
|
26
|
+
|
|
27
|
+
// WARN: Order is important
|
|
28
|
+
export const RECORDS_OF_SUB_SCHEMAS = [
|
|
29
|
+
"$defs",
|
|
30
|
+
"definitions",
|
|
31
|
+
"properties",
|
|
32
|
+
"patternProperties",
|
|
33
|
+
"dependencies",
|
|
34
|
+
] as const satisfies SchemaKeys;
|
|
35
|
+
|
|
36
|
+
export const SET_OF_RECORDS_OF_SUB_SCHEMAS = new Set(RECORDS_OF_SUB_SCHEMAS);
|
|
37
|
+
|
|
38
|
+
export type SubSchemasRecordKey = (typeof RECORDS_OF_SUB_SCHEMAS)[number];
|
|
39
|
+
|
|
40
|
+
// WARN: Order is important
|
|
41
|
+
export const ARRAYS_OF_SUB_SCHEMAS = [
|
|
42
|
+
"items",
|
|
43
|
+
"allOf",
|
|
44
|
+
"oneOf",
|
|
45
|
+
"anyOf",
|
|
46
|
+
] as const satisfies SchemaKeys;
|
|
47
|
+
|
|
48
|
+
export const SET_OF_ARRAYS_OF_SUB_SCHEMAS = new Set(ARRAYS_OF_SUB_SCHEMAS);
|
|
49
|
+
|
|
50
|
+
export type SubSchemasArrayKey = (typeof ARRAYS_OF_SUB_SCHEMAS)[number];
|
|
51
|
+
|
|
52
|
+
// WARN: Order is important
|
|
53
|
+
export const SUB_SCHEMAS = [
|
|
54
|
+
"items",
|
|
55
|
+
"additionalItems",
|
|
56
|
+
"additionalProperties",
|
|
57
|
+
"propertyNames",
|
|
58
|
+
"contains",
|
|
59
|
+
"if",
|
|
60
|
+
"then",
|
|
61
|
+
"else",
|
|
62
|
+
"not",
|
|
63
|
+
] as const satisfies SchemaKeys;
|
|
64
|
+
|
|
65
|
+
export const SET_OF_SUB_SCHEMAS = new Set(SUB_SCHEMAS);
|
|
66
|
+
|
|
67
|
+
export type SubSchemaKey = (typeof SUB_SCHEMAS)[number];
|
|
68
|
+
|
|
69
|
+
// WARN: Order is important
|
|
70
|
+
export const ALL_SUB_SCHEMA_KEYS = [
|
|
71
|
+
...RECORDS_OF_SUB_SCHEMAS,
|
|
72
|
+
...ARRAYS_OF_SUB_SCHEMAS,
|
|
73
|
+
...SUB_SCHEMAS,
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
export type AnySubSchemaKey = (typeof ALL_SUB_SCHEMA_KEYS)[number];
|
|
77
|
+
|
|
78
|
+
export type TransformedSchema<R, S> = Omit<S, AnySubSchemaKey> & {
|
|
79
|
+
items?: R | R[] | undefined;
|
|
80
|
+
additionalItems?: R | undefined;
|
|
81
|
+
contains?: R | undefined;
|
|
82
|
+
additionalProperties?: R | undefined;
|
|
83
|
+
propertyNames?: R | undefined;
|
|
84
|
+
if?: R | undefined;
|
|
85
|
+
then?: R | undefined;
|
|
86
|
+
else?: R | undefined;
|
|
87
|
+
not?: R | undefined;
|
|
88
|
+
// Records
|
|
89
|
+
$defs?: Record<string, R> | undefined;
|
|
90
|
+
properties?: Record<string, R> | undefined;
|
|
91
|
+
patternProperties?: Record<string, R> | undefined;
|
|
92
|
+
dependencies?: Record<string, R | string[]> | undefined;
|
|
93
|
+
definitions?: Record<string, R> | undefined;
|
|
94
|
+
// Arrays
|
|
95
|
+
allOf?: R[] | undefined;
|
|
96
|
+
anyOf?: R[] | undefined;
|
|
97
|
+
oneOf?: R[] | undefined;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type TransformedSchemaDefinition<R, S> =
|
|
101
|
+
TransformedSchema<R, S> | boolean;
|
|
102
|
+
|
|
103
|
+
export function isSchemaObject<D extends JSONSchema7Definition>(
|
|
104
|
+
schemaDef: D
|
|
105
|
+
): schemaDef is Exclude<D, boolean> {
|
|
106
|
+
return typeof schemaDef === "object";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isAllowAnySchema(
|
|
110
|
+
def: JSONSchema7Definition
|
|
111
|
+
): def is true | Record<string, never> {
|
|
112
|
+
return isSchemaObject(def) ? isRecordEmpty(def) : def === true;
|
|
113
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { JSONSchema7Definition } from "json-schema";
|
|
2
|
+
|
|
3
|
+
import { transformSchemaDefinition } from "../transform.ts";
|
|
4
|
+
|
|
5
|
+
function getAllOfSchemas(
|
|
6
|
+
schema: JSONSchema7Definition
|
|
7
|
+
): JSONSchema7Definition[] {
|
|
8
|
+
const result: JSONSchema7Definition[] = [];
|
|
9
|
+
const stack: JSONSchema7Definition[] = [schema];
|
|
10
|
+
while (stack.length > 0) {
|
|
11
|
+
const current = stack.pop()!;
|
|
12
|
+
if (typeof current === "boolean" || current.allOf === undefined) {
|
|
13
|
+
result.push(current);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const { allOf, ...rest } = current;
|
|
17
|
+
result.push(rest);
|
|
18
|
+
for (let i = allOf.length - 1; i >= 0; i--) {
|
|
19
|
+
stack.push(allOf[i]!);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createShallowAllOfMerge(
|
|
26
|
+
mergeArrayOfSchemaDefinitions: (
|
|
27
|
+
defs: JSONSchema7Definition[]
|
|
28
|
+
) => JSONSchema7Definition
|
|
29
|
+
) {
|
|
30
|
+
return (schema: JSONSchema7Definition) =>
|
|
31
|
+
mergeArrayOfSchemaDefinitions(getAllOfSchemas(schema));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createDeepAllOfMerge(
|
|
35
|
+
shallowMerge: (def: JSONSchema7Definition) => JSONSchema7Definition
|
|
36
|
+
) {
|
|
37
|
+
return (schemaDef: JSONSchema7Definition) =>
|
|
38
|
+
transformSchemaDefinition<JSONSchema7Definition>(schemaDef, (def) => {
|
|
39
|
+
if (typeof def === "boolean" || def.allOf === undefined) {
|
|
40
|
+
return def;
|
|
41
|
+
}
|
|
42
|
+
return shallowMerge(def);
|
|
43
|
+
});
|
|
44
|
+
}
|