@orpc/json-schema 1.14.10 → 1.14.11
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 +52 -48
- package/dist/index.d.mts +169 -17
- package/dist/index.d.ts +169 -17
- package/dist/index.mjs +837 -150
- package/package.json +8 -8
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,163 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { get, isPlainObject, tryOrUndefined, toArray, isDeepEqual, omit, isTypescriptObject } from '@orpc/shared';
|
|
2
|
+
export { Format as JsonSchemaFormat, TypeName as JsonSchemaType } from 'json-schema-typed/draft-2020-12';
|
|
3
|
+
import { ORPCError, cloneORPCError } from '@orpc/client';
|
|
4
|
+
import { getProcedureContractOrThrow } from '@orpc/contract';
|
|
5
|
+
|
|
6
|
+
const JSON_SCHEMA_LOGIC_KEYWORDS = /* @__PURE__ */ new Set([
|
|
7
|
+
"$dynamicRef",
|
|
8
|
+
"$ref",
|
|
9
|
+
"additionalItems",
|
|
10
|
+
"additionalProperties",
|
|
11
|
+
"allOf",
|
|
12
|
+
"anyOf",
|
|
13
|
+
"const",
|
|
14
|
+
"contains",
|
|
15
|
+
"contentEncoding",
|
|
16
|
+
"contentMediaType",
|
|
17
|
+
"contentSchema",
|
|
18
|
+
"dependencies",
|
|
19
|
+
"dependentRequired",
|
|
20
|
+
"dependentSchemas",
|
|
21
|
+
"else",
|
|
22
|
+
"enum",
|
|
23
|
+
"exclusiveMaximum",
|
|
24
|
+
"exclusiveMinimum",
|
|
25
|
+
"format",
|
|
26
|
+
"if",
|
|
27
|
+
"items",
|
|
28
|
+
"maxContains",
|
|
29
|
+
"maximum",
|
|
30
|
+
"maxItems",
|
|
31
|
+
"maxLength",
|
|
32
|
+
"maxProperties",
|
|
33
|
+
"minContains",
|
|
34
|
+
"minimum",
|
|
35
|
+
"minItems",
|
|
36
|
+
"minLength",
|
|
37
|
+
"minProperties",
|
|
38
|
+
"multipleOf",
|
|
39
|
+
"not",
|
|
40
|
+
"oneOf",
|
|
41
|
+
"pattern",
|
|
42
|
+
"patternProperties",
|
|
43
|
+
"prefixItems",
|
|
44
|
+
"properties",
|
|
45
|
+
"propertyNames",
|
|
46
|
+
"required",
|
|
47
|
+
"then",
|
|
48
|
+
"type",
|
|
49
|
+
"unevaluatedItems",
|
|
50
|
+
"unevaluatedProperties",
|
|
51
|
+
"uniqueItems"
|
|
52
|
+
]);
|
|
53
|
+
const JSON_SCHEMA_PRIMITIVE_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
54
|
+
const JSON_SCHEMA_RECORD_KEYWORDS = /* @__PURE__ */ new Set(["properties", "patternProperties", "dependentSchemas", "dependencies", "$defs"]);
|
|
55
|
+
|
|
56
|
+
function encodeJsonPointerSegment(segment) {
|
|
57
|
+
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
58
|
+
}
|
|
59
|
+
function decodeJsonPointerSegment(segment) {
|
|
60
|
+
return segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
61
|
+
}
|
|
62
|
+
function visitJsonSchemaRefs(value, visit, schemaLevel = true, seen = /* @__PURE__ */ new Set()) {
|
|
63
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
seen.add(value);
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
for (const item of value) {
|
|
69
|
+
visitJsonSchemaRefs(item, visit, schemaLevel, seen);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
for (const [key, val] of Object.entries(value)) {
|
|
74
|
+
if (key === "$ref" && typeof val === "string") {
|
|
75
|
+
visit(val);
|
|
76
|
+
} else if (!schemaLevel) {
|
|
77
|
+
visitJsonSchemaRefs(val, visit, true, seen);
|
|
78
|
+
} else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
|
|
79
|
+
visitJsonSchemaRefs(val, visit, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), seen);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function mapJsonSchemaRefs(value, map, schemaLevel = true, path = []) {
|
|
84
|
+
if (!value || typeof value !== "object") {
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
return value.map((item, index) => mapJsonSchemaRefs(item, map, schemaLevel, [...path, index]));
|
|
89
|
+
}
|
|
90
|
+
const result = {};
|
|
91
|
+
for (const [key, val] of Object.entries(value)) {
|
|
92
|
+
if (key === "$ref" && typeof val === "string") {
|
|
93
|
+
result[key] = map(val, [...path, key]);
|
|
94
|
+
} else if (!schemaLevel) {
|
|
95
|
+
result[key] = mapJsonSchemaRefs(val, map, true, [...path, key]);
|
|
96
|
+
} else if (JSON_SCHEMA_LOGIC_KEYWORDS.has(key) || JSON_SCHEMA_RECORD_KEYWORDS.has(key)) {
|
|
97
|
+
result[key] = mapJsonSchemaRefs(val, map, !JSON_SCHEMA_RECORD_KEYWORDS.has(key), [...path, key]);
|
|
98
|
+
} else {
|
|
99
|
+
result[key] = val;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
function hoistRecursiveRefToDef(schema) {
|
|
105
|
+
if (typeof schema !== "object") {
|
|
106
|
+
return schema;
|
|
107
|
+
}
|
|
108
|
+
let defName;
|
|
109
|
+
const rewritten = mapJsonSchemaRefs(schema, (ref) => {
|
|
110
|
+
if (ref === "#" || ref.startsWith("#/") && !ref.startsWith("#/$defs/") && get(schema, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
|
|
111
|
+
defName ??= findRecursiveJsonSchemaDefName(schema.$defs);
|
|
112
|
+
return `#/$defs/${encodeJsonPointerSegment(defName)}${ref.slice(1)}`;
|
|
113
|
+
}
|
|
114
|
+
return ref;
|
|
115
|
+
});
|
|
116
|
+
if (defName === void 0) {
|
|
117
|
+
return schema;
|
|
118
|
+
}
|
|
119
|
+
const { $defs, ...rest } = rewritten;
|
|
120
|
+
return {
|
|
121
|
+
$ref: `#/$defs/${encodeJsonPointerSegment(defName)}`,
|
|
122
|
+
$defs: {
|
|
123
|
+
...$defs,
|
|
124
|
+
[defName]: rest
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function resolveJsonSchemaRootLocalRef(schema, $defs) {
|
|
129
|
+
if (typeof schema === "boolean") {
|
|
130
|
+
return schema;
|
|
131
|
+
}
|
|
132
|
+
if (arguments.length === 1) {
|
|
133
|
+
$defs = schema.$defs;
|
|
134
|
+
}
|
|
135
|
+
if (!$defs) {
|
|
136
|
+
return schema;
|
|
137
|
+
}
|
|
138
|
+
if (typeof schema.$ref !== "string" || !schema.$ref.startsWith("#/$defs/")) {
|
|
139
|
+
return schema;
|
|
140
|
+
}
|
|
141
|
+
const resolved = get($defs, schema.$ref.slice("#/$defs/".length).split("/").map(decodeJsonPointerSegment));
|
|
142
|
+
if (resolved === void 0) {
|
|
143
|
+
return schema;
|
|
144
|
+
}
|
|
145
|
+
if (typeof resolved !== "object") {
|
|
146
|
+
return resolved;
|
|
147
|
+
}
|
|
148
|
+
const { $ref: _ref, ...rest } = schema;
|
|
149
|
+
return resolveJsonSchemaRootLocalRef({
|
|
150
|
+
...rest,
|
|
151
|
+
...resolved
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function findRecursiveJsonSchemaDefName(defs) {
|
|
155
|
+
let index = 0;
|
|
156
|
+
while (defs?.[`__schema${index}`] !== void 0) {
|
|
157
|
+
index++;
|
|
158
|
+
}
|
|
159
|
+
return `__schema${index}`;
|
|
160
|
+
}
|
|
3
161
|
|
|
4
162
|
var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
|
|
5
163
|
JsonSchemaXNativeType2["BigInt"] = "bigint";
|
|
@@ -11,87 +169,101 @@ var JsonSchemaXNativeType = /* @__PURE__ */ ((JsonSchemaXNativeType2) => {
|
|
|
11
169
|
return JsonSchemaXNativeType2;
|
|
12
170
|
})(JsonSchemaXNativeType || {});
|
|
13
171
|
|
|
14
|
-
const
|
|
172
|
+
const UNSATISFIED = 0;
|
|
173
|
+
const LOOSELY_SATISFIED = 1;
|
|
174
|
+
const SATISFIED = 2;
|
|
175
|
+
function minSatisfaction(a, b) {
|
|
176
|
+
return a < b ? a : b;
|
|
177
|
+
}
|
|
15
178
|
class JsonSchemaCoercer {
|
|
16
|
-
coerce(schema,
|
|
17
|
-
|
|
179
|
+
coerce([schema, optional], value) {
|
|
180
|
+
if (optional && value === void 0) {
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
const [, coerced] = this.coerceInternal(schema, schema, value);
|
|
18
184
|
return coerced;
|
|
19
185
|
}
|
|
20
|
-
|
|
186
|
+
coerceInternal(rootSchema, schema, value, appliedRefs) {
|
|
21
187
|
if (typeof schema === "boolean") {
|
|
22
|
-
return [schema,
|
|
188
|
+
return [schema ? SATISFIED : UNSATISFIED, value];
|
|
23
189
|
}
|
|
24
190
|
if (Array.isArray(schema.type)) {
|
|
25
|
-
return this
|
|
26
|
-
|
|
27
|
-
|
|
191
|
+
return this.coerceInternal(
|
|
192
|
+
rootSchema,
|
|
193
|
+
{ anyOf: schema.type.map((type) => ({ ...schema, type })) },
|
|
194
|
+
value,
|
|
195
|
+
appliedRefs
|
|
196
|
+
);
|
|
28
197
|
}
|
|
29
|
-
let coerced =
|
|
30
|
-
let satisfied =
|
|
198
|
+
let coerced = value;
|
|
199
|
+
let satisfied = SATISFIED;
|
|
31
200
|
if (typeof schema.$ref === "string") {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
|
|
201
|
+
const resolved = schema.$ref.startsWith("#/") ? get(rootSchema, schema.$ref.slice("#/".length).split("/").map(decodeJsonPointerSegment)) : schema.$ref === "#" ? rootSchema : void 0;
|
|
202
|
+
if (resolved !== void 0 && !appliedRefs?.has(resolved)) {
|
|
203
|
+
appliedRefs ??= /* @__PURE__ */ new Set();
|
|
204
|
+
appliedRefs.add(resolved);
|
|
205
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, resolved, coerced, appliedRefs);
|
|
206
|
+
appliedRefs.delete(resolved);
|
|
35
207
|
coerced = subCoerced;
|
|
36
|
-
satisfied = subSatisfied;
|
|
208
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
37
209
|
}
|
|
38
210
|
}
|
|
39
211
|
const enumValues = schema.const !== void 0 ? [schema.const] : schema.enum;
|
|
40
212
|
if (enumValues !== void 0 && !enumValues.includes(coerced)) {
|
|
41
213
|
if (typeof coerced === "string") {
|
|
42
|
-
const numberValue =
|
|
214
|
+
const numberValue = stringToNumber(coerced);
|
|
43
215
|
if (enumValues.includes(numberValue)) {
|
|
44
216
|
coerced = numberValue;
|
|
45
217
|
} else {
|
|
46
|
-
const booleanValue =
|
|
218
|
+
const booleanValue = stringToBoolean(coerced);
|
|
47
219
|
if (enumValues.includes(booleanValue)) {
|
|
48
220
|
coerced = booleanValue;
|
|
49
221
|
} else {
|
|
50
|
-
satisfied =
|
|
222
|
+
satisfied = UNSATISFIED;
|
|
51
223
|
}
|
|
52
224
|
}
|
|
53
225
|
} else {
|
|
54
|
-
satisfied =
|
|
226
|
+
satisfied = UNSATISFIED;
|
|
55
227
|
}
|
|
56
228
|
}
|
|
57
|
-
if (
|
|
229
|
+
if (schema.type) {
|
|
58
230
|
switch (schema.type) {
|
|
59
231
|
case "null": {
|
|
60
232
|
if (coerced !== null) {
|
|
61
|
-
satisfied =
|
|
233
|
+
satisfied = UNSATISFIED;
|
|
62
234
|
}
|
|
63
235
|
break;
|
|
64
236
|
}
|
|
65
237
|
case "string": {
|
|
66
238
|
if (typeof coerced !== "string") {
|
|
67
|
-
satisfied =
|
|
239
|
+
satisfied = UNSATISFIED;
|
|
68
240
|
}
|
|
69
241
|
break;
|
|
70
242
|
}
|
|
71
243
|
case "number": {
|
|
72
244
|
if (typeof coerced === "string") {
|
|
73
|
-
coerced =
|
|
245
|
+
coerced = stringToNumber(coerced);
|
|
74
246
|
}
|
|
75
247
|
if (typeof coerced !== "number") {
|
|
76
|
-
satisfied =
|
|
248
|
+
satisfied = UNSATISFIED;
|
|
77
249
|
}
|
|
78
250
|
break;
|
|
79
251
|
}
|
|
80
252
|
case "integer": {
|
|
81
253
|
if (typeof coerced === "string") {
|
|
82
|
-
coerced =
|
|
254
|
+
coerced = stringToInteger(coerced);
|
|
83
255
|
}
|
|
84
256
|
if (typeof coerced !== "number" || !Number.isInteger(coerced)) {
|
|
85
|
-
satisfied =
|
|
257
|
+
satisfied = UNSATISFIED;
|
|
86
258
|
}
|
|
87
259
|
break;
|
|
88
260
|
}
|
|
89
261
|
case "boolean": {
|
|
90
262
|
if (typeof coerced === "string") {
|
|
91
|
-
coerced =
|
|
263
|
+
coerced = stringToBoolean(coerced);
|
|
92
264
|
}
|
|
93
265
|
if (typeof coerced !== "boolean") {
|
|
94
|
-
satisfied =
|
|
266
|
+
satisfied = UNSATISFIED;
|
|
95
267
|
}
|
|
96
268
|
break;
|
|
97
269
|
}
|
|
@@ -103,26 +275,24 @@ class JsonSchemaCoercer {
|
|
|
103
275
|
const coercedItems = coerced.map((item, i) => {
|
|
104
276
|
const subSchema = prefixItemSchemas[i] ?? itemSchema;
|
|
105
277
|
if (subSchema === void 0) {
|
|
106
|
-
satisfied =
|
|
278
|
+
satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
|
|
107
279
|
return item;
|
|
108
280
|
}
|
|
109
|
-
const [subSatisfied, subCoerced] = this
|
|
110
|
-
|
|
111
|
-
satisfied = false;
|
|
112
|
-
}
|
|
281
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, item);
|
|
282
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
113
283
|
if (subCoerced !== item) {
|
|
114
284
|
shouldUseCoercedItems = true;
|
|
115
285
|
}
|
|
116
286
|
return subCoerced;
|
|
117
287
|
});
|
|
118
288
|
if (coercedItems.length < prefixItemSchemas.length) {
|
|
119
|
-
satisfied =
|
|
289
|
+
satisfied = UNSATISFIED;
|
|
120
290
|
}
|
|
121
291
|
if (shouldUseCoercedItems) {
|
|
122
292
|
coerced = coercedItems;
|
|
123
293
|
}
|
|
124
294
|
} else {
|
|
125
|
-
satisfied =
|
|
295
|
+
satisfied = UNSATISFIED;
|
|
126
296
|
}
|
|
127
297
|
break;
|
|
128
298
|
}
|
|
@@ -130,37 +300,38 @@ class JsonSchemaCoercer {
|
|
|
130
300
|
if (Array.isArray(coerced)) {
|
|
131
301
|
coerced = { ...coerced };
|
|
132
302
|
}
|
|
133
|
-
if (
|
|
303
|
+
if (isPlainObject(coerced)) {
|
|
134
304
|
let shouldUseCoercedItems = false;
|
|
135
|
-
const coercedItems =
|
|
136
|
-
const patternProperties = Object.entries(schema.patternProperties ?? {}).
|
|
305
|
+
const coercedItems = { ...coerced };
|
|
306
|
+
const patternProperties = Object.entries(schema.patternProperties ?? {}).flatMap(([key, value2]) => {
|
|
307
|
+
const pattern = tryOrUndefined(() => new RegExp(key));
|
|
308
|
+
return pattern ? [[pattern, value2]] : [];
|
|
309
|
+
});
|
|
310
|
+
const propertySchemas = schema.properties;
|
|
137
311
|
for (const key in coerced) {
|
|
138
|
-
const
|
|
139
|
-
const subSchema = (
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
if (subCoerced !== value) {
|
|
152
|
-
shouldUseCoercedItems = true;
|
|
312
|
+
const value2 = coerced[key];
|
|
313
|
+
const subSchema = (propertySchemas !== void 0 && Object.hasOwn(propertySchemas, key) ? propertySchemas[key] : void 0) ?? patternProperties.find(([pattern]) => pattern.test(key))?.[1] ?? schema.additionalProperties;
|
|
314
|
+
if (value2 !== void 0 || schema.required?.includes(key)) {
|
|
315
|
+
if (subSchema === void 0) {
|
|
316
|
+
satisfied = minSatisfaction(satisfied, LOOSELY_SATISFIED);
|
|
317
|
+
} else {
|
|
318
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, value2);
|
|
319
|
+
coercedItems[key] = subCoerced;
|
|
320
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
321
|
+
if (subCoerced !== value2) {
|
|
322
|
+
shouldUseCoercedItems = true;
|
|
323
|
+
}
|
|
153
324
|
}
|
|
154
325
|
}
|
|
155
326
|
}
|
|
156
327
|
if (schema.required?.some((key) => !Object.hasOwn(coercedItems, key))) {
|
|
157
|
-
satisfied =
|
|
328
|
+
satisfied = UNSATISFIED;
|
|
158
329
|
}
|
|
159
330
|
if (shouldUseCoercedItems) {
|
|
160
331
|
coerced = coercedItems;
|
|
161
332
|
}
|
|
162
333
|
} else {
|
|
163
|
-
satisfied =
|
|
334
|
+
satisfied = UNSATISFIED;
|
|
164
335
|
}
|
|
165
336
|
break;
|
|
166
337
|
}
|
|
@@ -170,60 +341,60 @@ class JsonSchemaCoercer {
|
|
|
170
341
|
switch (schema["x-native-type"]) {
|
|
171
342
|
case JsonSchemaXNativeType.Date: {
|
|
172
343
|
if (typeof coerced === "string") {
|
|
173
|
-
coerced =
|
|
344
|
+
coerced = stringToDate(coerced);
|
|
174
345
|
}
|
|
175
346
|
if (!(coerced instanceof Date)) {
|
|
176
|
-
satisfied =
|
|
347
|
+
satisfied = UNSATISFIED;
|
|
177
348
|
}
|
|
178
349
|
break;
|
|
179
350
|
}
|
|
180
351
|
case JsonSchemaXNativeType.BigInt: {
|
|
181
352
|
switch (typeof coerced) {
|
|
182
353
|
case "string":
|
|
183
|
-
coerced =
|
|
354
|
+
coerced = stringToBigInt(coerced);
|
|
184
355
|
break;
|
|
185
356
|
case "number":
|
|
186
|
-
coerced =
|
|
357
|
+
coerced = numberToBigInt(coerced);
|
|
187
358
|
break;
|
|
188
359
|
}
|
|
189
360
|
if (typeof coerced !== "bigint") {
|
|
190
|
-
satisfied =
|
|
361
|
+
satisfied = UNSATISFIED;
|
|
191
362
|
}
|
|
192
363
|
break;
|
|
193
364
|
}
|
|
194
365
|
case JsonSchemaXNativeType.RegExp: {
|
|
195
366
|
if (typeof coerced === "string") {
|
|
196
|
-
coerced =
|
|
367
|
+
coerced = stringToRegExp(coerced);
|
|
197
368
|
}
|
|
198
369
|
if (!(coerced instanceof RegExp)) {
|
|
199
|
-
satisfied =
|
|
370
|
+
satisfied = UNSATISFIED;
|
|
200
371
|
}
|
|
201
372
|
break;
|
|
202
373
|
}
|
|
203
374
|
case JsonSchemaXNativeType.Url: {
|
|
204
375
|
if (typeof coerced === "string") {
|
|
205
|
-
coerced =
|
|
376
|
+
coerced = stringToURL(coerced);
|
|
206
377
|
}
|
|
207
378
|
if (!(coerced instanceof URL)) {
|
|
208
|
-
satisfied =
|
|
379
|
+
satisfied = UNSATISFIED;
|
|
209
380
|
}
|
|
210
381
|
break;
|
|
211
382
|
}
|
|
212
383
|
case JsonSchemaXNativeType.Set: {
|
|
213
384
|
if (Array.isArray(coerced)) {
|
|
214
|
-
coerced =
|
|
385
|
+
coerced = arrayToSet(coerced);
|
|
215
386
|
}
|
|
216
387
|
if (!(coerced instanceof Set)) {
|
|
217
|
-
satisfied =
|
|
388
|
+
satisfied = UNSATISFIED;
|
|
218
389
|
}
|
|
219
390
|
break;
|
|
220
391
|
}
|
|
221
392
|
case JsonSchemaXNativeType.Map: {
|
|
222
393
|
if (Array.isArray(coerced)) {
|
|
223
|
-
coerced =
|
|
394
|
+
coerced = arrayToMap(coerced);
|
|
224
395
|
}
|
|
225
396
|
if (!(coerced instanceof Map)) {
|
|
226
|
-
satisfied =
|
|
397
|
+
satisfied = UNSATISFIED;
|
|
227
398
|
}
|
|
228
399
|
break;
|
|
229
400
|
}
|
|
@@ -231,133 +402,649 @@ class JsonSchemaCoercer {
|
|
|
231
402
|
}
|
|
232
403
|
if (schema.allOf) {
|
|
233
404
|
for (const subSchema of schema.allOf) {
|
|
234
|
-
const [subSatisfied, subCoerced] = this
|
|
405
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
|
|
235
406
|
coerced = subCoerced;
|
|
236
|
-
|
|
237
|
-
satisfied = false;
|
|
238
|
-
}
|
|
407
|
+
satisfied = minSatisfaction(satisfied, subSatisfied);
|
|
239
408
|
}
|
|
240
409
|
}
|
|
241
410
|
for (const key of ["anyOf", "oneOf"]) {
|
|
242
411
|
if (schema[key]) {
|
|
243
|
-
let
|
|
412
|
+
let best;
|
|
244
413
|
for (const subSchema of schema[key]) {
|
|
245
|
-
const [subSatisfied, subCoerced] = this
|
|
246
|
-
if (subSatisfied) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
414
|
+
const [subSatisfied, subCoerced] = this.coerceInternal(rootSchema, subSchema, coerced, appliedRefs);
|
|
415
|
+
if (subSatisfied === UNSATISFIED) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (subCoerced === coerced) {
|
|
419
|
+
best = { satisfied: subSatisfied, value: subCoerced };
|
|
420
|
+
break;
|
|
253
421
|
}
|
|
422
|
+
if (!best || subSatisfied > best.satisfied) {
|
|
423
|
+
best = { satisfied: subSatisfied, value: subCoerced };
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (best) {
|
|
427
|
+
coerced = best.value;
|
|
428
|
+
satisfied = minSatisfaction(satisfied, best.satisfied);
|
|
429
|
+
} else {
|
|
430
|
+
satisfied = UNSATISFIED;
|
|
254
431
|
}
|
|
255
|
-
coerced = bestOptions ? bestOptions.coerced : coerced;
|
|
256
|
-
satisfied = bestOptions ? bestOptions.satisfied : false;
|
|
257
432
|
}
|
|
258
433
|
}
|
|
259
434
|
if (typeof schema.not !== "undefined") {
|
|
260
|
-
const [notSatisfied] = this
|
|
261
|
-
if (notSatisfied) {
|
|
262
|
-
satisfied =
|
|
435
|
+
const [notSatisfied] = this.coerceInternal(rootSchema, schema.not, coerced, appliedRefs);
|
|
436
|
+
if (notSatisfied !== UNSATISFIED) {
|
|
437
|
+
satisfied = UNSATISFIED;
|
|
263
438
|
}
|
|
264
439
|
}
|
|
265
440
|
return [satisfied, coerced];
|
|
266
441
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
442
|
+
}
|
|
443
|
+
const NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
|
|
444
|
+
function stringToNumber(value) {
|
|
445
|
+
if (!NUMBER_PATTERN.test(value)) {
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
const num = Number(value);
|
|
449
|
+
if (num < Number.MIN_SAFE_INTEGER || num > Number.MAX_SAFE_INTEGER) {
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
return num;
|
|
453
|
+
}
|
|
454
|
+
const INTEGER_PATTERN = /^-?(?:0|[1-9]\d*)$/;
|
|
455
|
+
function stringToInteger(value) {
|
|
456
|
+
if (!INTEGER_PATTERN.test(value)) {
|
|
457
|
+
return value;
|
|
458
|
+
}
|
|
459
|
+
const num = Number(value);
|
|
460
|
+
if (!Number.isSafeInteger(num)) {
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
return num;
|
|
464
|
+
}
|
|
465
|
+
function stringToBigInt(value) {
|
|
466
|
+
if (!INTEGER_PATTERN.test(value)) {
|
|
467
|
+
return value;
|
|
468
|
+
}
|
|
469
|
+
return BigInt(value);
|
|
470
|
+
}
|
|
471
|
+
function numberToBigInt(value) {
|
|
472
|
+
if (!Number.isInteger(value)) {
|
|
473
|
+
return value;
|
|
474
|
+
}
|
|
475
|
+
return BigInt(value);
|
|
476
|
+
}
|
|
477
|
+
function stringToBoolean(value) {
|
|
478
|
+
const lower = value.toLowerCase();
|
|
479
|
+
if (lower === "false" || lower === "off") {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (lower === "true" || lower === "on") {
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
return value;
|
|
486
|
+
}
|
|
487
|
+
const DATE_TIME_PATTERN = /^[+-]?\d{4,6}-\d{1,2}-\d{1,2}(?:[T ].*)?$/;
|
|
488
|
+
function stringToDate(value) {
|
|
489
|
+
if (!DATE_TIME_PATTERN.test(value)) {
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
492
|
+
const date = new Date(value);
|
|
493
|
+
if (Number.isNaN(date.getTime())) {
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
return date;
|
|
497
|
+
}
|
|
498
|
+
const REGEXP_PATTERN = /^\/([\s\S]*)\/([a-z]*)$/;
|
|
499
|
+
function stringToRegExp(value) {
|
|
500
|
+
const match = value.match(REGEXP_PATTERN);
|
|
501
|
+
if (match) {
|
|
502
|
+
const [, pattern, flags] = match;
|
|
503
|
+
return tryOrUndefined(() => new RegExp(pattern, flags)) ?? value;
|
|
504
|
+
}
|
|
505
|
+
return value;
|
|
506
|
+
}
|
|
507
|
+
function stringToURL(value) {
|
|
508
|
+
return tryOrUndefined(() => new URL(value)) ?? value;
|
|
509
|
+
}
|
|
510
|
+
function arrayToSet(value) {
|
|
511
|
+
const set = new Set(value);
|
|
512
|
+
if (set.size !== value.length) {
|
|
513
|
+
return value;
|
|
514
|
+
}
|
|
515
|
+
return set;
|
|
516
|
+
}
|
|
517
|
+
function arrayToMap(value) {
|
|
518
|
+
if (value.some((item) => !Array.isArray(item) || item.length !== 2)) {
|
|
519
|
+
return value;
|
|
520
|
+
}
|
|
521
|
+
const result = new Map(value);
|
|
522
|
+
if (result.size !== value.length) {
|
|
523
|
+
return value;
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function isJsonPrimitiveSchema(schema) {
|
|
529
|
+
if (typeof schema === "boolean") {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
if (typeof schema.type === "string" && JSON_SCHEMA_PRIMITIVE_TYPES.has(schema.type)) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
if (schema.const !== void 0) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
if (schema.enum !== void 0) {
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
function isJsonFileSchema(schema) {
|
|
544
|
+
return typeof schema !== "boolean" && schema.type === "string" && (typeof schema.contentMediaType === "string" || schema.format === "binary" || schema.contentEncoding === "binary");
|
|
545
|
+
}
|
|
546
|
+
function isJsonObjectSchema(schema) {
|
|
547
|
+
return typeof schema !== "boolean" && schema.type === "object";
|
|
548
|
+
}
|
|
549
|
+
function isJsonArraySchema(schema) {
|
|
550
|
+
return typeof schema !== "boolean" && schema.type === "array";
|
|
551
|
+
}
|
|
552
|
+
function isUnconstrainedSchema(schema) {
|
|
553
|
+
if (typeof schema === "boolean") {
|
|
554
|
+
return schema;
|
|
555
|
+
}
|
|
556
|
+
if (Object.keys(schema).every((k) => !JSON_SCHEMA_LOGIC_KEYWORDS.has(k))) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
function ensureJsonSchemaObject(schema) {
|
|
562
|
+
if (typeof schema === "boolean") {
|
|
563
|
+
return schema ? {} : { not: {} };
|
|
564
|
+
}
|
|
565
|
+
return schema;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function combineJsonSchemasWithComposition(keyword, schemas) {
|
|
569
|
+
if (schemas.length <= 1) {
|
|
570
|
+
return schemas[0] ?? true;
|
|
571
|
+
}
|
|
572
|
+
const mergedDefs = {};
|
|
573
|
+
const compositionBranches = [];
|
|
574
|
+
for (let i = 0; i < schemas.length; i++) {
|
|
575
|
+
const schema = schemas[i];
|
|
576
|
+
if (typeof schema === "boolean") {
|
|
577
|
+
compositionBranches.push(schema);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const { $defs, ...rest } = schema;
|
|
581
|
+
const renameMap = {};
|
|
582
|
+
const promotedNames = /* @__PURE__ */ new Set();
|
|
583
|
+
if ($defs) {
|
|
584
|
+
for (const [name, def] of Object.entries($defs)) {
|
|
585
|
+
if (def === void 0)
|
|
586
|
+
continue;
|
|
587
|
+
promotedNames.add(name);
|
|
588
|
+
if (name in mergedDefs) {
|
|
589
|
+
if (isDeepEqual(mergedDefs[name], def)) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
let counter = 2;
|
|
593
|
+
let newName = `${name}${counter}`;
|
|
594
|
+
while (newName in mergedDefs) {
|
|
595
|
+
counter++;
|
|
596
|
+
newName = `${name}${counter}`;
|
|
597
|
+
}
|
|
598
|
+
mergedDefs[newName] = def;
|
|
599
|
+
renameMap[name] = newName;
|
|
600
|
+
} else {
|
|
601
|
+
mergedDefs[name] = def;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
271
604
|
}
|
|
272
|
-
|
|
605
|
+
compositionBranches.push(mapJsonSchemaRefs(
|
|
606
|
+
rest,
|
|
607
|
+
(ref) => {
|
|
608
|
+
if (ref === "#") {
|
|
609
|
+
return `#/${keyword}/${i}`;
|
|
610
|
+
}
|
|
611
|
+
if (ref.startsWith("#/$defs/")) {
|
|
612
|
+
const afterPrefix = ref.slice("#/$defs/".length);
|
|
613
|
+
const slashIdx = afterPrefix.indexOf("/");
|
|
614
|
+
const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
|
|
615
|
+
const rest2 = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
|
|
616
|
+
const defName = decodeJsonPointerSegment(encodedSegment);
|
|
617
|
+
if (!promotedNames.has(defName)) {
|
|
618
|
+
return ref;
|
|
619
|
+
}
|
|
620
|
+
if (defName in renameMap) {
|
|
621
|
+
return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${rest2}`;
|
|
622
|
+
}
|
|
623
|
+
return ref;
|
|
624
|
+
}
|
|
625
|
+
if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
|
|
626
|
+
return `#/${keyword}/${i}/${ref.slice(2)}`;
|
|
627
|
+
}
|
|
628
|
+
return ref;
|
|
629
|
+
}
|
|
630
|
+
));
|
|
273
631
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
632
|
+
const result = { [keyword]: compositionBranches };
|
|
633
|
+
if (Object.keys(mergedDefs).length > 0) {
|
|
634
|
+
result.$defs = mergedDefs;
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
function combineJsonObjectSchemaEntries(entries) {
|
|
639
|
+
const properties = {};
|
|
640
|
+
const required = [];
|
|
641
|
+
const mergedDefs = {};
|
|
642
|
+
for (const [name, propertySchema, optional] of entries) {
|
|
643
|
+
if (!optional) {
|
|
644
|
+
required.push(name);
|
|
645
|
+
}
|
|
646
|
+
if (typeof propertySchema === "boolean") {
|
|
647
|
+
properties[name] = propertySchema;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const { $defs, ...rest } = propertySchema;
|
|
651
|
+
const renameMap = {};
|
|
652
|
+
const promotedNames = /* @__PURE__ */ new Set();
|
|
653
|
+
if ($defs) {
|
|
654
|
+
for (const [defName, def] of Object.entries($defs)) {
|
|
655
|
+
if (def === void 0) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
promotedNames.add(defName);
|
|
659
|
+
if (defName in mergedDefs) {
|
|
660
|
+
if (isDeepEqual(mergedDefs[defName], def)) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
let counter = 2;
|
|
664
|
+
let newName = `${defName}${counter}`;
|
|
665
|
+
while (newName in mergedDefs) {
|
|
666
|
+
counter++;
|
|
667
|
+
newName = `${defName}${counter}`;
|
|
668
|
+
}
|
|
669
|
+
mergedDefs[newName] = def;
|
|
670
|
+
renameMap[defName] = newName;
|
|
671
|
+
} else {
|
|
672
|
+
mergedDefs[defName] = def;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
278
675
|
}
|
|
279
|
-
|
|
676
|
+
const propertyPathPrefix = `#/properties/${encodeJsonPointerSegment(name)}`;
|
|
677
|
+
properties[name] = mapJsonSchemaRefs(rest, (ref) => {
|
|
678
|
+
if (ref.startsWith("#/$defs/")) {
|
|
679
|
+
const afterPrefix = ref.slice("#/$defs/".length);
|
|
680
|
+
const slashIdx = afterPrefix.indexOf("/");
|
|
681
|
+
const encodedSegment = slashIdx === -1 ? afterPrefix : afterPrefix.slice(0, slashIdx);
|
|
682
|
+
const refRest = slashIdx === -1 ? "" : afterPrefix.slice(slashIdx);
|
|
683
|
+
const defName = decodeJsonPointerSegment(encodedSegment);
|
|
684
|
+
if (!promotedNames.has(defName)) {
|
|
685
|
+
return ref;
|
|
686
|
+
}
|
|
687
|
+
if (defName in renameMap) {
|
|
688
|
+
return `#/$defs/${encodeJsonPointerSegment(renameMap[defName])}${refRest}`;
|
|
689
|
+
}
|
|
690
|
+
return ref;
|
|
691
|
+
}
|
|
692
|
+
if (ref === "#") {
|
|
693
|
+
return propertyPathPrefix;
|
|
694
|
+
}
|
|
695
|
+
if (ref.startsWith("#/") && get(rest, ref.slice(2).split("/").map(decodeJsonPointerSegment)) !== void 0) {
|
|
696
|
+
return `${propertyPathPrefix}/${ref.slice(2)}`;
|
|
697
|
+
}
|
|
698
|
+
return ref;
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
const schema = {
|
|
702
|
+
type: "object",
|
|
703
|
+
properties
|
|
704
|
+
};
|
|
705
|
+
if (required.length > 0) {
|
|
706
|
+
schema.required = required;
|
|
707
|
+
}
|
|
708
|
+
if (Object.keys(mergedDefs).length > 0) {
|
|
709
|
+
schema.$defs = mergedDefs;
|
|
710
|
+
}
|
|
711
|
+
return schema;
|
|
712
|
+
}
|
|
713
|
+
function extractJsonObjectSchemaEntries(schema) {
|
|
714
|
+
schema = hoistRecursiveRefToDef(schema);
|
|
715
|
+
if (typeof schema !== "object") {
|
|
716
|
+
return void 0;
|
|
280
717
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
718
|
+
const result = extractJsonObjectSchemaEntriesInternal(omit(schema, ["$defs"]), schema.$defs, /* @__PURE__ */ new Set());
|
|
719
|
+
if (!result.objectLike) {
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
return result.entries.map(([n, s, ...r]) => [n, withRootDefs(s, schema.$defs), ...r]);
|
|
723
|
+
}
|
|
724
|
+
function extractJsonObjectSchemaEntriesInternal(schema, $defs, resolvingRefs) {
|
|
725
|
+
if (typeof schema !== "object") {
|
|
726
|
+
return { entries: [], objectLike: false };
|
|
727
|
+
}
|
|
728
|
+
if (typeof schema.$ref === "string") {
|
|
729
|
+
if (resolvingRefs.has(schema.$ref)) {
|
|
730
|
+
return { entries: [], objectLike: true };
|
|
285
731
|
}
|
|
286
|
-
|
|
287
|
-
|
|
732
|
+
const resolved = resolveJsonSchemaRootLocalRef(schema, $defs);
|
|
733
|
+
if (resolved !== schema) {
|
|
734
|
+
return extractJsonObjectSchemaEntriesInternal(resolved, $defs, new Set(resolvingRefs).add(schema.$ref));
|
|
288
735
|
}
|
|
289
|
-
return value;
|
|
290
736
|
}
|
|
291
|
-
|
|
292
|
-
|
|
737
|
+
const sources = [];
|
|
738
|
+
if (schema.properties) {
|
|
739
|
+
sources.push({
|
|
740
|
+
entries: Object.entries(schema.properties).map(([name, propertySchema]) => {
|
|
741
|
+
return [
|
|
742
|
+
name,
|
|
743
|
+
propertySchema,
|
|
744
|
+
!schema.required?.includes(name)
|
|
745
|
+
];
|
|
746
|
+
}),
|
|
747
|
+
source: "direct"
|
|
748
|
+
});
|
|
293
749
|
}
|
|
294
|
-
|
|
295
|
-
|
|
750
|
+
let objectLike = schema.type === "object" || schema.properties !== void 0 || schema.required !== void 0 || schema.additionalProperties !== void 0;
|
|
751
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
752
|
+
const branches = schema[keyword];
|
|
753
|
+
if (branches === void 0) {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
const branchResults = branches.map((branch) => extractJsonObjectSchemaEntriesInternal(branch, $defs, resolvingRefs));
|
|
757
|
+
if (branchResults.some((result) => !result.objectLike)) {
|
|
758
|
+
return { entries: [], objectLike: false };
|
|
759
|
+
}
|
|
760
|
+
const entriesByName = /* @__PURE__ */ new Map();
|
|
761
|
+
for (const result of branchResults) {
|
|
762
|
+
for (const entry of result.entries) {
|
|
763
|
+
const entries = entriesByName.get(entry[0]);
|
|
764
|
+
if (entries) {
|
|
765
|
+
entries.push(entry);
|
|
766
|
+
} else {
|
|
767
|
+
entriesByName.set(entry[0], [entry]);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
objectLike = true;
|
|
772
|
+
sources.push({
|
|
773
|
+
entries: Array.from(entriesByName.entries()).map(([name, entries]) => {
|
|
774
|
+
const schemas = deduplicateJsonSchemas(entries.map((entry) => entry[1]));
|
|
775
|
+
const required = keyword === "allOf" ? entries.some((entry) => !entry[2]) : branchResults.every((result) => {
|
|
776
|
+
const entry = result.entries.find((item) => item[0] === name);
|
|
777
|
+
return entry !== void 0 && !entry[2];
|
|
778
|
+
});
|
|
779
|
+
return [
|
|
780
|
+
name,
|
|
781
|
+
schemas.length === 1 ? schemas[0] : keyword === "allOf" ? { allOf: schemas } : { anyOf: schemas },
|
|
782
|
+
!required
|
|
783
|
+
];
|
|
784
|
+
}),
|
|
785
|
+
source: keyword
|
|
786
|
+
});
|
|
296
787
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
788
|
+
const sourceEntries = /* @__PURE__ */ new Map();
|
|
789
|
+
for (const { entries, source } of sources) {
|
|
790
|
+
for (const entry of entries) {
|
|
791
|
+
const existing = sourceEntries.get(entry[0]);
|
|
792
|
+
if (existing) {
|
|
793
|
+
existing[source] = entry;
|
|
794
|
+
} else {
|
|
795
|
+
sourceEntries.set(entry[0], { [source]: entry });
|
|
796
|
+
}
|
|
301
797
|
}
|
|
302
|
-
return date;
|
|
303
798
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
799
|
+
return {
|
|
800
|
+
entries: Array.from(sourceEntries.entries()).map(([name, entries]) => {
|
|
801
|
+
const schemas = deduplicateJsonSchemas([
|
|
802
|
+
entries.direct?.[1],
|
|
803
|
+
entries.allOf?.[1],
|
|
804
|
+
entries.anyOf?.[1],
|
|
805
|
+
entries.oneOf?.[1]
|
|
806
|
+
].filter((schema2) => schema2 !== void 0));
|
|
807
|
+
return [
|
|
808
|
+
name,
|
|
809
|
+
schemas.length === 1 ? schemas[0] : { allOf: schemas },
|
|
810
|
+
[entries.direct, entries.allOf, entries.anyOf, entries.oneOf].every((entry) => entry === void 0 || entry[2])
|
|
811
|
+
];
|
|
812
|
+
}),
|
|
813
|
+
objectLike
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function flattenJsonUnionSchema(schema) {
|
|
817
|
+
return deduplicateJsonSchemas(flattenJsonUnionSchemaInternal(schema, /* @__PURE__ */ new Set()));
|
|
818
|
+
}
|
|
819
|
+
function flattenJsonUnionSchemaInternal(schema, resolvingRefs) {
|
|
820
|
+
if (typeof schema !== "object") {
|
|
821
|
+
return [schema];
|
|
822
|
+
}
|
|
823
|
+
if (typeof schema.$ref === "string") {
|
|
824
|
+
if (resolvingRefs.has(schema.$ref)) {
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
const resolved = resolveJsonSchemaRootLocalRef(schema);
|
|
828
|
+
if (resolved !== schema) {
|
|
829
|
+
const result = flattenJsonUnionSchemaInternal(resolved, resolvingRefs.add(schema.$ref));
|
|
830
|
+
if (result.length > 1) {
|
|
831
|
+
return result;
|
|
832
|
+
}
|
|
309
833
|
}
|
|
310
|
-
return value;
|
|
311
834
|
}
|
|
312
|
-
|
|
313
|
-
|
|
835
|
+
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = schema;
|
|
836
|
+
const entries = Object.entries(rest).filter(([, val]) => val !== void 0);
|
|
837
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
838
|
+
if (schema[keyword]) {
|
|
839
|
+
return schema[keyword].flatMap((s) => {
|
|
840
|
+
s = ensureJsonSchemaObject(s);
|
|
841
|
+
const mergedSchema = {
|
|
842
|
+
...s,
|
|
843
|
+
...Object.fromEntries(entries.filter(([key]) => s[key] === void 0)),
|
|
844
|
+
$defs: schema.$defs
|
|
845
|
+
};
|
|
846
|
+
const conflicts = entries.filter(([key]) => s[key] !== void 0);
|
|
847
|
+
if (conflicts.length) {
|
|
848
|
+
mergedSchema.allOf = [...toArray(mergedSchema.allOf), Object.fromEntries(conflicts)];
|
|
849
|
+
}
|
|
850
|
+
return flattenJsonUnionSchemaInternal(mergedSchema, resolvingRefs);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
314
853
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
854
|
+
return [schema];
|
|
855
|
+
}
|
|
856
|
+
function matchArrayableJsonSchema(schema) {
|
|
857
|
+
const schemas = flattenJsonUnionSchema(schema);
|
|
858
|
+
if (schemas.length !== 2) {
|
|
859
|
+
return void 0;
|
|
860
|
+
}
|
|
861
|
+
const arraySchema = schemas.find(isJsonArraySchema);
|
|
862
|
+
if (arraySchema === void 0) {
|
|
863
|
+
return void 0;
|
|
864
|
+
}
|
|
865
|
+
const items1 = arraySchema.items ?? true;
|
|
866
|
+
const items2 = schemas.find((s) => s !== arraySchema);
|
|
867
|
+
const logicItem1 = Object.fromEntries(
|
|
868
|
+
Object.entries(ensureJsonSchemaObject(items1)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
|
|
869
|
+
);
|
|
870
|
+
const logicItem2 = Object.fromEntries(
|
|
871
|
+
Object.entries(ensureJsonSchemaObject(items2)).filter(([key]) => JSON_SCHEMA_LOGIC_KEYWORDS.has(key))
|
|
872
|
+
);
|
|
873
|
+
if (!isDeepEqual(logicItem1, logicItem2)) {
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
876
|
+
return [items2, arraySchema];
|
|
877
|
+
}
|
|
878
|
+
function deduplicateJsonSchemas(schemas) {
|
|
879
|
+
const result = [];
|
|
880
|
+
for (const schema of schemas) {
|
|
881
|
+
if (result.some((i) => isDeepEqual(i, schema))) {
|
|
882
|
+
continue;
|
|
319
883
|
}
|
|
320
|
-
|
|
884
|
+
result.push(schema);
|
|
321
885
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
886
|
+
return result;
|
|
887
|
+
}
|
|
888
|
+
function withRootDefs(schema, $defs) {
|
|
889
|
+
if (typeof schema === "boolean" || !$defs) {
|
|
890
|
+
return schema;
|
|
891
|
+
}
|
|
892
|
+
return { ...schema, $defs };
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
class DelegatingJsonSchemaConverter {
|
|
896
|
+
constructor(converters = []) {
|
|
897
|
+
this.converters = converters;
|
|
898
|
+
}
|
|
899
|
+
convert(schema, direction) {
|
|
900
|
+
for (const converter of this.converters) {
|
|
901
|
+
if (converter.condition(schema, direction)) {
|
|
902
|
+
return converter.convert(schema, direction);
|
|
903
|
+
}
|
|
325
904
|
}
|
|
326
|
-
const result =
|
|
327
|
-
|
|
328
|
-
|
|
905
|
+
const result = schema?.["~standard"].validate(void 0);
|
|
906
|
+
const optional = result instanceof Promise ? false : !result?.issues?.length;
|
|
907
|
+
if (schema && "jsonSchema" in schema["~standard"] && schema["~standard"].jsonSchema) {
|
|
908
|
+
try {
|
|
909
|
+
return [
|
|
910
|
+
schema["~standard"].jsonSchema[direction](),
|
|
911
|
+
optional
|
|
912
|
+
];
|
|
913
|
+
} catch {
|
|
914
|
+
}
|
|
329
915
|
}
|
|
330
|
-
return
|
|
916
|
+
return [{}, optional];
|
|
331
917
|
}
|
|
332
918
|
}
|
|
333
919
|
|
|
334
|
-
class
|
|
920
|
+
class StandardJsonSchemaConverter {
|
|
921
|
+
condition(schema, _direction) {
|
|
922
|
+
return Boolean(
|
|
923
|
+
schema && "jsonSchema" in schema["~standard"] && isTypescriptObject(schema["~standard"].jsonSchema) && "input" in schema["~standard"].jsonSchema && typeof schema["~standard"].jsonSchema.input === "function" && "output" in schema["~standard"].jsonSchema && typeof schema["~standard"].jsonSchema.output === "function"
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
convert(schema, direction) {
|
|
927
|
+
return this.convertInternal(schema, direction);
|
|
928
|
+
}
|
|
929
|
+
convertInternal(schema, direction) {
|
|
930
|
+
try {
|
|
931
|
+
const jsonSchema = direction === "input" ? schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }) : schema["~standard"].jsonSchema.output({ target: "draft-2020-12" });
|
|
932
|
+
let optional = false;
|
|
933
|
+
try {
|
|
934
|
+
const result = schema["~standard"].validate(void 0);
|
|
935
|
+
if (!(result instanceof Promise) && !result.issues) {
|
|
936
|
+
optional = direction === "input" ? true : result.value === void 0;
|
|
937
|
+
}
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
return [jsonSchema, optional];
|
|
941
|
+
} catch {
|
|
942
|
+
return [{}, true];
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
class SmartCoercionHandlerPlugin {
|
|
948
|
+
name = "~smart-coercion";
|
|
335
949
|
converter;
|
|
336
950
|
coercer;
|
|
337
951
|
cache = /* @__PURE__ */ new WeakMap();
|
|
338
952
|
constructor(options = {}) {
|
|
339
|
-
this.converter = new
|
|
953
|
+
this.converter = new DelegatingJsonSchemaConverter([
|
|
954
|
+
...toArray(options.converters),
|
|
955
|
+
new StandardJsonSchemaConverter()
|
|
956
|
+
]);
|
|
340
957
|
this.coercer = new JsonSchemaCoercer();
|
|
341
958
|
}
|
|
342
959
|
init(options) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
960
|
+
return {
|
|
961
|
+
...options,
|
|
962
|
+
clientInterceptors: [
|
|
963
|
+
({ next, input, ...interceptorOptions }) => {
|
|
964
|
+
const inputSchemas = interceptorOptions.procedure["~orpc"].inputSchemas;
|
|
965
|
+
if (!inputSchemas) {
|
|
966
|
+
return next();
|
|
967
|
+
}
|
|
968
|
+
const coercedInput = this.coerceValue(inputSchemas, input);
|
|
969
|
+
return next({ ...interceptorOptions, input: coercedInput });
|
|
970
|
+
},
|
|
971
|
+
...toArray(options.clientInterceptors)
|
|
972
|
+
]
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
coerceValue(schemas, value) {
|
|
976
|
+
for (const schema of schemas) {
|
|
977
|
+
let converted = this.cache.get(schema);
|
|
978
|
+
if (!converted) {
|
|
979
|
+
converted = this.converter.convert(schema, "input");
|
|
980
|
+
this.cache.set(schema, converted);
|
|
348
981
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
982
|
+
value = this.coercer.coerce(converted, value);
|
|
983
|
+
}
|
|
984
|
+
return value;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
class SmartCoercionLinkPlugin {
|
|
989
|
+
constructor(contract, options = {}) {
|
|
990
|
+
this.contract = contract;
|
|
991
|
+
this.converter = new DelegatingJsonSchemaConverter([
|
|
992
|
+
...toArray(options.converters),
|
|
993
|
+
new StandardJsonSchemaConverter()
|
|
994
|
+
]);
|
|
995
|
+
this.coercer = new JsonSchemaCoercer();
|
|
352
996
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
997
|
+
name = "~smart-coercion";
|
|
998
|
+
/**
|
|
999
|
+
* Output and error values should be coerced before validation.
|
|
1000
|
+
*/
|
|
1001
|
+
after = ["~response-validation"];
|
|
1002
|
+
converter;
|
|
1003
|
+
coercer;
|
|
1004
|
+
cache = /* @__PURE__ */ new WeakMap();
|
|
1005
|
+
init(options) {
|
|
1006
|
+
return {
|
|
1007
|
+
...options,
|
|
1008
|
+
interceptors: [
|
|
1009
|
+
...toArray(options.interceptors),
|
|
1010
|
+
async ({ next, path }) => {
|
|
1011
|
+
const procedure = getProcedureContractOrThrow(this.contract, path);
|
|
1012
|
+
try {
|
|
1013
|
+
const output = await next();
|
|
1014
|
+
const outputSchemas = procedure["~orpc"].outputSchemas;
|
|
1015
|
+
if (!outputSchemas) {
|
|
1016
|
+
return output;
|
|
1017
|
+
}
|
|
1018
|
+
const coercedOutput = this.coerceValue(outputSchemas, output);
|
|
1019
|
+
return coercedOutput;
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (!(error instanceof ORPCError) || !error.defined) {
|
|
1022
|
+
throw error;
|
|
1023
|
+
}
|
|
1024
|
+
const errorMap = procedure["~orpc"].errorMap;
|
|
1025
|
+
const dataSchema = errorMap[error.code]?.data;
|
|
1026
|
+
if (!dataSchema) {
|
|
1027
|
+
throw error;
|
|
1028
|
+
}
|
|
1029
|
+
const cloned = cloneORPCError(error);
|
|
1030
|
+
cloned.data = this.coerceValue([dataSchema], cloned.data);
|
|
1031
|
+
throw cloned;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
]
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
coerceValue(schemas, value) {
|
|
1038
|
+
for (const schema of schemas) {
|
|
1039
|
+
let converted = this.cache.get(schema);
|
|
1040
|
+
if (!converted) {
|
|
1041
|
+
converted = this.converter.convert(schema, "output");
|
|
1042
|
+
this.cache.set(schema, converted);
|
|
1043
|
+
}
|
|
1044
|
+
value = this.coercer.coerce(converted, value);
|
|
358
1045
|
}
|
|
359
|
-
return
|
|
1046
|
+
return value;
|
|
360
1047
|
}
|
|
361
1048
|
}
|
|
362
1049
|
|
|
363
|
-
export { JsonSchemaCoercer, JsonSchemaXNativeType,
|
|
1050
|
+
export { DelegatingJsonSchemaConverter, JsonSchemaCoercer, JsonSchemaXNativeType, SmartCoercionHandlerPlugin, SmartCoercionLinkPlugin, StandardJsonSchemaConverter, combineJsonObjectSchemaEntries, combineJsonSchemasWithComposition, decodeJsonPointerSegment, deduplicateJsonSchemas, encodeJsonPointerSegment, ensureJsonSchemaObject, extractJsonObjectSchemaEntries, flattenJsonUnionSchema, hoistRecursiveRefToDef, isJsonArraySchema, isJsonFileSchema, isJsonObjectSchema, isJsonPrimitiveSchema, isUnconstrainedSchema, mapJsonSchemaRefs, matchArrayableJsonSchema, resolveJsonSchemaRootLocalRef, visitJsonSchemaRefs };
|