@kubb/adapter-oas 5.0.0-beta.93 → 5.0.0-beta.94
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/dist/index.cjs +644 -645
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +644 -645
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import { access, readFile } from "node:fs/promises";
|
|
6
6
|
import { compileErrors, validate } from "@readme/openapi-parser";
|
|
7
7
|
import { upgrade } from "@scalar/openapi-upgrader";
|
|
8
|
-
import { parse } from "yaml";
|
|
9
8
|
import { bundle } from "api-ref-bundler";
|
|
9
|
+
import { parse } from "yaml";
|
|
10
10
|
//#region src/constants.ts
|
|
11
11
|
/**
|
|
12
12
|
* Default parser options applied when no explicit options are provided.
|
|
@@ -344,7 +344,7 @@ async function read(path) {
|
|
|
344
344
|
return readFile(path, { encoding: "utf8" });
|
|
345
345
|
}
|
|
346
346
|
//#endregion
|
|
347
|
-
//#region src/
|
|
347
|
+
//#region src/factory.ts
|
|
348
348
|
const urlRegExp = /^https?:\/+/i;
|
|
349
349
|
async function readSource(sourcePath) {
|
|
350
350
|
if (urlRegExp.test(sourcePath)) {
|
|
@@ -387,8 +387,6 @@ async function bundleDocument(pathOrUrl) {
|
|
|
387
387
|
await resolver(pathOrUrl);
|
|
388
388
|
return await bundle(pathOrUrl, resolver);
|
|
389
389
|
}
|
|
390
|
-
//#endregion
|
|
391
|
-
//#region src/factory.ts
|
|
392
390
|
/**
|
|
393
391
|
* Loads and bundles an OpenAPI document, returning the raw `Document`.
|
|
394
392
|
*
|
|
@@ -458,19 +456,12 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
458
456
|
}
|
|
459
457
|
}
|
|
460
458
|
//#endregion
|
|
461
|
-
//#region src/
|
|
459
|
+
//#region src/oas.ts
|
|
462
460
|
/**
|
|
463
461
|
* Returns `true` when a schema should be treated as nullable.
|
|
464
462
|
*
|
|
465
463
|
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
466
464
|
* `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
|
|
467
|
-
*
|
|
468
|
-
* @example
|
|
469
|
-
* ```ts
|
|
470
|
-
* isNullable({ type: 'string', nullable: true }) // true
|
|
471
|
-
* isNullable({ type: ['string', 'null'] }) // true
|
|
472
|
-
* isNullable({ type: 'string' }) // false
|
|
473
|
-
* ```
|
|
474
465
|
*/
|
|
475
466
|
function isNullable(schema) {
|
|
476
467
|
if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
|
|
@@ -481,24 +472,13 @@ function isNullable(schema) {
|
|
|
481
472
|
}
|
|
482
473
|
/**
|
|
483
474
|
* Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
|
|
484
|
-
*
|
|
485
|
-
* @example
|
|
486
|
-
* ```ts
|
|
487
|
-
* isReference({ $ref: '#/components/schemas/Pet' }) // true
|
|
488
|
-
* isReference({ type: 'string' }) // false
|
|
489
|
-
* ```
|
|
490
475
|
*/
|
|
491
476
|
function isReference(obj) {
|
|
492
477
|
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
493
478
|
}
|
|
494
479
|
/**
|
|
495
|
-
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object
|
|
496
|
-
*
|
|
497
|
-
* @example
|
|
498
|
-
* ```ts
|
|
499
|
-
* isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
|
|
500
|
-
* isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
|
|
501
|
-
* ```
|
|
480
|
+
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
|
|
481
|
+
* excluding the Swagger 2 string form.
|
|
502
482
|
*/
|
|
503
483
|
function isDiscriminator(obj) {
|
|
504
484
|
const record = obj;
|
|
@@ -506,18 +486,10 @@ function isDiscriminator(obj) {
|
|
|
506
486
|
}
|
|
507
487
|
/**
|
|
508
488
|
* Returns `true` when a schema is a binary payload: an octet-stream string body.
|
|
509
|
-
*
|
|
510
|
-
* @example
|
|
511
|
-
* ```ts
|
|
512
|
-
* isBinary({ type: 'string', contentMediaType: 'application/octet-stream' }) // true
|
|
513
|
-
* isBinary({ type: 'string' }) // false
|
|
514
|
-
* ```
|
|
515
489
|
*/
|
|
516
490
|
function isBinary(schema) {
|
|
517
491
|
return schema.type === "string" && schema.contentMediaType === "application/octet-stream";
|
|
518
492
|
}
|
|
519
|
-
//#endregion
|
|
520
|
-
//#region src/mime.ts
|
|
521
493
|
/**
|
|
522
494
|
* MIME type fragments that mark a media type as JSON-like.
|
|
523
495
|
*
|
|
@@ -610,6 +582,23 @@ function dereferenceWithRef(document, schema) {
|
|
|
610
582
|
};
|
|
611
583
|
return schema;
|
|
612
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Resolves a `$ref` slot in place: when `container[key]` holds a `$ref`, replaces it with the
|
|
587
|
+
* resolved value and returns that value. Returns `null` when the slot is empty, cannot be resolved,
|
|
588
|
+
* or is still a `$ref` after resolving. A non-`$ref` value is returned untouched, without writing.
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```ts
|
|
592
|
+
* derefInPlace<ResponseObject>({ document, container: operation.schema.responses, key: '200' })
|
|
593
|
+
* ```
|
|
594
|
+
*/
|
|
595
|
+
function derefInPlace({ document, container, key }) {
|
|
596
|
+
const value = container[key];
|
|
597
|
+
if (!isReference(value)) return value ? value : null;
|
|
598
|
+
const resolved = resolveRef(document, value.$ref);
|
|
599
|
+
container[key] = resolved;
|
|
600
|
+
return resolved && !isReference(resolved) ? resolved : null;
|
|
601
|
+
}
|
|
613
602
|
//#endregion
|
|
614
603
|
//#region src/operation.ts
|
|
615
604
|
/**
|
|
@@ -641,31 +630,22 @@ function getResponseStatusCodes({ schema }) {
|
|
|
641
630
|
function getResponseByStatusCode({ document, operation, statusCode }) {
|
|
642
631
|
const responses = operation.schema.responses;
|
|
643
632
|
if (!responses || isReference(responses)) return false;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (!resolved || isReference(resolved)) return false;
|
|
650
|
-
return resolved;
|
|
651
|
-
}
|
|
652
|
-
return response;
|
|
633
|
+
return derefInPlace({
|
|
634
|
+
document,
|
|
635
|
+
container: responses,
|
|
636
|
+
key: statusCode
|
|
637
|
+
}) ?? false;
|
|
653
638
|
}
|
|
654
639
|
/**
|
|
655
640
|
* Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
|
|
656
641
|
* `undefined` when the operation has no request body.
|
|
657
642
|
*/
|
|
658
643
|
function getRequestBodyContent({ document, operation }) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
schema.requestBody = resolved;
|
|
665
|
-
if (!resolved || isReference(resolved)) return;
|
|
666
|
-
requestBody = resolved;
|
|
667
|
-
}
|
|
668
|
-
return requestBody.content;
|
|
644
|
+
return derefInPlace({
|
|
645
|
+
document,
|
|
646
|
+
container: operation.schema,
|
|
647
|
+
key: "requestBody"
|
|
648
|
+
})?.content;
|
|
669
649
|
}
|
|
670
650
|
/**
|
|
671
651
|
* Returns the request body media type. With `mediaType` set, returns that entry or `false`.
|
|
@@ -714,14 +694,12 @@ function getOperations(document) {
|
|
|
714
694
|
if (!paths) return operations;
|
|
715
695
|
for (const path of Object.keys(paths)) {
|
|
716
696
|
if (path.startsWith("x-")) continue;
|
|
717
|
-
|
|
697
|
+
const pathItem = derefInPlace({
|
|
698
|
+
document,
|
|
699
|
+
container: paths,
|
|
700
|
+
key: path
|
|
701
|
+
});
|
|
718
702
|
if (!pathItem) continue;
|
|
719
|
-
if (isReference(pathItem)) {
|
|
720
|
-
const resolved = resolveRef(document, pathItem.$ref);
|
|
721
|
-
paths[path] = resolved;
|
|
722
|
-
if (!resolved || isReference(resolved)) continue;
|
|
723
|
-
pathItem = resolved;
|
|
724
|
-
}
|
|
725
703
|
const item = pathItem;
|
|
726
704
|
for (const method of Object.keys(item)) {
|
|
727
705
|
if (!SUPPORTED_METHODS.has(method)) continue;
|
|
@@ -849,6 +827,15 @@ function getResponseBody(responseBody, contentType) {
|
|
|
849
827
|
if (!availableContentType) return false;
|
|
850
828
|
return body.content[availableContentType];
|
|
851
829
|
}
|
|
830
|
+
function resolveResponseRefs(document, operation) {
|
|
831
|
+
const responses = operation.schema.responses;
|
|
832
|
+
if (!responses) return;
|
|
833
|
+
for (const key in responses) derefInPlace({
|
|
834
|
+
document,
|
|
835
|
+
container: responses,
|
|
836
|
+
key
|
|
837
|
+
});
|
|
838
|
+
}
|
|
852
839
|
/**
|
|
853
840
|
* Returns the response schema for a given operation and HTTP status code.
|
|
854
841
|
*
|
|
@@ -860,14 +847,6 @@ function getResponseBody(responseBody, contentType) {
|
|
|
860
847
|
* getResponseSchema(document, operation, '4XX') // {}
|
|
861
848
|
* ```
|
|
862
849
|
*/
|
|
863
|
-
function resolveResponseRefs(document, operation) {
|
|
864
|
-
const responses = operation.schema.responses;
|
|
865
|
-
if (!responses) return;
|
|
866
|
-
for (const key in responses) {
|
|
867
|
-
const schema = responses[key];
|
|
868
|
-
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
850
|
function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
872
851
|
resolveResponseRefs(document, operation);
|
|
873
852
|
const responseBody = getResponseBody(getResponseByStatusCode({
|
|
@@ -906,6 +885,16 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
906
885
|
return dereferenceWithRef(document, schema);
|
|
907
886
|
}
|
|
908
887
|
/**
|
|
888
|
+
* Returns `true` when `fragment` carries any JSON Schema keyword that makes it
|
|
889
|
+
* structurally significant on its own (see `structuralKeys`).
|
|
890
|
+
*
|
|
891
|
+
* A fragment with a structural keyword can't be safely merged into a parent schema.
|
|
892
|
+
*/
|
|
893
|
+
function hasStructuralKeywords(fragment) {
|
|
894
|
+
for (const key in fragment) if (structuralKeys.has(key)) return true;
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
909
898
|
* Flattens a keyword-only `allOf` into its parent schema.
|
|
910
899
|
*
|
|
911
900
|
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
@@ -924,16 +913,6 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
924
913
|
* // returned unchanged, contains a $ref
|
|
925
914
|
* ```
|
|
926
915
|
*/
|
|
927
|
-
/**
|
|
928
|
-
* Returns `true` when `fragment` carries any JSON Schema keyword that makes it
|
|
929
|
-
* structurally significant on its own (see `structuralKeys`).
|
|
930
|
-
*
|
|
931
|
-
* A fragment with a structural keyword can't be safely merged into a parent schema.
|
|
932
|
-
*/
|
|
933
|
-
function hasStructuralKeywords(fragment) {
|
|
934
|
-
for (const key in fragment) if (structuralKeys.has(key)) return true;
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
916
|
function flattenSchema(schema) {
|
|
938
917
|
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
|
|
939
918
|
const allOfFragments = schema.allOf;
|
|
@@ -1185,7 +1164,7 @@ function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
|
1185
1164
|
return body.content ? Object.keys(body.content) : [];
|
|
1186
1165
|
}
|
|
1187
1166
|
//#endregion
|
|
1188
|
-
//#region src/
|
|
1167
|
+
//#region src/converters.ts
|
|
1189
1168
|
/**
|
|
1190
1169
|
* Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
|
|
1191
1170
|
*
|
|
@@ -1209,7 +1188,7 @@ function normalizeArrayEnum(schema) {
|
|
|
1209
1188
|
* Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
|
|
1210
1189
|
* and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
|
|
1211
1190
|
*/
|
|
1212
|
-
function
|
|
1191
|
+
function createNullNode(schema, name, nullable) {
|
|
1213
1192
|
return ast.factory.createSchema({
|
|
1214
1193
|
type: "null",
|
|
1215
1194
|
primitive: "null",
|
|
@@ -1239,620 +1218,636 @@ function nameEnums(node, options) {
|
|
|
1239
1218
|
return named;
|
|
1240
1219
|
}
|
|
1241
1220
|
/**
|
|
1242
|
-
*
|
|
1221
|
+
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1243
1222
|
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
|
|
1223
|
+
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
1224
|
+
* (description, readOnly, nullable, etc.) are stored directly on the ref node.
|
|
1225
|
+
* Use `syncSchemaRef(node)` in printers to get a merged view of both.
|
|
1226
|
+
* Circular refs are detected in `resolveRefNode` and leave `schema` as `null`.
|
|
1227
|
+
*/
|
|
1228
|
+
function convertRef({ schema, name, nullable, defaultValue, rawOptions, document, resolveRefNode, refExists }) {
|
|
1229
|
+
const refPath = schema.$ref;
|
|
1230
|
+
const resolvedSchema = refPath ? resolveRefNode(refPath, rawOptions) : null;
|
|
1231
|
+
if (refPath && document.components && !refExists(refPath)) return ast.factory.createSchema({
|
|
1232
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1233
|
+
type: "unknown"
|
|
1234
|
+
});
|
|
1235
|
+
return ast.factory.createSchema({
|
|
1236
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1237
|
+
type: "ref",
|
|
1238
|
+
name: extractRefName(schema.$ref),
|
|
1239
|
+
ref: schema.$ref,
|
|
1240
|
+
schema: resolvedSchema
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
|
|
1249
1245
|
*/
|
|
1250
|
-
function
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1261
|
-
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1262
|
-
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1263
|
-
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1264
|
-
*/
|
|
1265
|
-
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1266
|
-
/**
|
|
1267
|
-
* Memoized record of whether a `$ref` path resolves to a node the document actually defines.
|
|
1268
|
-
* A circular ref still resolves to an existing target, so this stays `true` for cycles and only
|
|
1269
|
-
* goes `false` for a `$ref` that points at a component the spec never declares.
|
|
1270
|
-
*/
|
|
1271
|
-
const refExistence = /* @__PURE__ */ new Map();
|
|
1272
|
-
function refExists(refPath) {
|
|
1273
|
-
if (!refExistence.has(refPath)) {
|
|
1274
|
-
let exists = false;
|
|
1275
|
-
try {
|
|
1276
|
-
exists = !!resolveRef(document, refPath);
|
|
1277
|
-
} catch {
|
|
1278
|
-
exists = false;
|
|
1279
|
-
}
|
|
1280
|
-
refExistence.set(refPath, exists);
|
|
1281
|
-
}
|
|
1282
|
-
return refExistence.get(refPath) ?? false;
|
|
1283
|
-
}
|
|
1284
|
-
/**
|
|
1285
|
-
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1286
|
-
*
|
|
1287
|
-
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
1288
|
-
* (description, readOnly, nullable, etc.) are stored directly on the ref node.
|
|
1289
|
-
* Use `syncSchemaRef(node)` in printers to get a merged view of both.
|
|
1290
|
-
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1291
|
-
*/
|
|
1292
|
-
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1293
|
-
let resolvedSchema = null;
|
|
1294
|
-
const refPath = schema.$ref;
|
|
1295
|
-
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1296
|
-
if (!resolvedRefCache.has(refPath)) {
|
|
1297
|
-
try {
|
|
1298
|
-
const referenced = resolveRef(document, refPath);
|
|
1299
|
-
if (referenced) {
|
|
1300
|
-
resolvingRefs.add(refPath);
|
|
1301
|
-
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1302
|
-
resolvingRefs.delete(refPath);
|
|
1303
|
-
}
|
|
1304
|
-
} catch {}
|
|
1305
|
-
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1306
|
-
}
|
|
1307
|
-
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1308
|
-
}
|
|
1309
|
-
if (refPath && document.components && !refExists(refPath)) return ast.factory.createSchema({
|
|
1310
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1311
|
-
type: "unknown"
|
|
1312
|
-
});
|
|
1246
|
+
function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
|
|
1247
|
+
if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
|
|
1248
|
+
const [memberSchema] = schema.allOf;
|
|
1249
|
+
const memberNode = parse({
|
|
1250
|
+
schema: memberSchema,
|
|
1251
|
+
name
|
|
1252
|
+
}, rawOptions);
|
|
1253
|
+
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1254
|
+
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
1255
|
+
const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
|
|
1313
1256
|
return ast.factory.createSchema({
|
|
1314
|
-
...
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1257
|
+
...memberNodeProps,
|
|
1258
|
+
name,
|
|
1259
|
+
title: schema.title ?? memberNode.title,
|
|
1260
|
+
description: schema.description ?? memberNode.description,
|
|
1261
|
+
deprecated: schema.deprecated ?? memberNode.deprecated,
|
|
1262
|
+
nullable: mergedNullable,
|
|
1263
|
+
readOnly: schema.readOnly ?? memberNode.readOnly,
|
|
1264
|
+
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1265
|
+
default: mergedDefault,
|
|
1266
|
+
examples: extractExamples(schema) ?? memberNode.examples,
|
|
1267
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1268
|
+
format: schema.format ?? memberNode.format
|
|
1319
1269
|
});
|
|
1320
1270
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
if (
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
name,
|
|
1337
|
-
title: schema.title ?? memberNode.title,
|
|
1338
|
-
description: schema.description ?? memberNode.description,
|
|
1339
|
-
deprecated: schema.deprecated ?? memberNode.deprecated,
|
|
1340
|
-
nullable: mergedNullable,
|
|
1341
|
-
readOnly: schema.readOnly ?? memberNode.readOnly,
|
|
1342
|
-
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1343
|
-
default: mergedDefault,
|
|
1344
|
-
examples: extractExamples(schema) ?? memberNode.examples,
|
|
1345
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1346
|
-
format: schema.format ?? memberNode.format
|
|
1271
|
+
const filteredDiscriminantValues = [];
|
|
1272
|
+
const allOfMembers = schema.allOf.filter((item) => {
|
|
1273
|
+
if (!isReference(item) || !name) return true;
|
|
1274
|
+
const deref = resolveRef(document, item.$ref);
|
|
1275
|
+
if (!deref || !isDiscriminator(deref)) return true;
|
|
1276
|
+
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1277
|
+
if (!parentUnion) return true;
|
|
1278
|
+
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1279
|
+
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1280
|
+
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1281
|
+
if (inOneOf || inMapping) {
|
|
1282
|
+
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
|
|
1283
|
+
if (discriminatorValue) filteredDiscriminantValues.push({
|
|
1284
|
+
propertyName: deref.discriminator.propertyName,
|
|
1285
|
+
value: discriminatorValue
|
|
1347
1286
|
});
|
|
1287
|
+
return false;
|
|
1348
1288
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1374
|
-
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1375
|
-
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1376
|
-
if (missingRequired.length) {
|
|
1377
|
-
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1378
|
-
if (!isReference(item)) return [item];
|
|
1379
|
-
const deref = resolveRef(document, item.$ref);
|
|
1380
|
-
return deref && !isReference(deref) ? [deref] : [];
|
|
1381
|
-
});
|
|
1382
|
-
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1383
|
-
allOfMembers.push(parseSchema({
|
|
1384
|
-
schema: {
|
|
1385
|
-
properties: { [key]: resolved.properties[key] },
|
|
1386
|
-
required: [key]
|
|
1387
|
-
},
|
|
1289
|
+
return true;
|
|
1290
|
+
}).map((s) => parse({
|
|
1291
|
+
schema: s,
|
|
1292
|
+
name
|
|
1293
|
+
}, rawOptions));
|
|
1294
|
+
const syntheticStart = allOfMembers.length;
|
|
1295
|
+
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1296
|
+
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1297
|
+
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1298
|
+
if (missingRequired.length) {
|
|
1299
|
+
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1300
|
+
if (!isReference(item)) return [item];
|
|
1301
|
+
const deref = resolveRef(document, item.$ref);
|
|
1302
|
+
return deref && !isReference(deref) ? [deref] : [];
|
|
1303
|
+
});
|
|
1304
|
+
for (const key of missingRequired) for (const resolved of resolvedMembers) {
|
|
1305
|
+
const prop = resolved.properties?.[key];
|
|
1306
|
+
if (prop) {
|
|
1307
|
+
const memberSchema = {
|
|
1308
|
+
properties: { [key]: prop },
|
|
1309
|
+
required: [key]
|
|
1310
|
+
};
|
|
1311
|
+
allOfMembers.push(parse({
|
|
1312
|
+
schema: memberSchema,
|
|
1388
1313
|
name
|
|
1389
1314
|
}, rawOptions));
|
|
1390
1315
|
break;
|
|
1391
1316
|
}
|
|
1392
1317
|
}
|
|
1393
1318
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1319
|
+
}
|
|
1320
|
+
if (schema.properties) {
|
|
1321
|
+
const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
|
|
1322
|
+
allOfMembers.push(parse({ schema: schemaWithoutAllOf }, rawOptions));
|
|
1323
|
+
}
|
|
1324
|
+
for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
|
|
1325
|
+
propertyName,
|
|
1326
|
+
value
|
|
1327
|
+
}));
|
|
1328
|
+
return ast.factory.createSchema({
|
|
1329
|
+
type: "intersection",
|
|
1330
|
+
members: [...mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1331
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
|
|
1336
|
+
*/
|
|
1337
|
+
function convertUnion({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
|
|
1338
|
+
function pickDiscriminatorPropertyNode(node, propertyName) {
|
|
1339
|
+
const discriminatorProperty = ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
|
|
1340
|
+
if (!discriminatorProperty) return null;
|
|
1402
1341
|
return ast.factory.createSchema({
|
|
1403
|
-
type: "
|
|
1404
|
-
|
|
1405
|
-
|
|
1342
|
+
type: "object",
|
|
1343
|
+
primitive: "object",
|
|
1344
|
+
properties: [discriminatorProperty]
|
|
1406
1345
|
});
|
|
1407
1346
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1347
|
+
function resolveRefSilent($ref) {
|
|
1348
|
+
if (!$ref.startsWith("#")) return null;
|
|
1349
|
+
return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
|
|
1350
|
+
}
|
|
1351
|
+
function implicitDiscriminantValue(member) {
|
|
1352
|
+
if (!discriminator || discriminator.mapping || !isReference(member)) return null;
|
|
1353
|
+
const value = extractRefName(member.$ref);
|
|
1354
|
+
if (!value) return null;
|
|
1355
|
+
const variant = resolveRefSilent(member.$ref);
|
|
1356
|
+
if (!variant) return null;
|
|
1357
|
+
const propertyName = discriminator.propertyName;
|
|
1358
|
+
const seen = /* @__PURE__ */ new Set([member.$ref]);
|
|
1359
|
+
function constrains(v) {
|
|
1360
|
+
const prop = v.properties?.[propertyName];
|
|
1361
|
+
const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
|
|
1362
|
+
if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
|
|
1363
|
+
const composition = v.allOf ?? v.oneOf ?? v.anyOf;
|
|
1364
|
+
if (!composition) return false;
|
|
1365
|
+
return composition.some((m) => {
|
|
1366
|
+
if (!isReference(m)) return constrains(m);
|
|
1367
|
+
if (seen.has(m.$ref)) return false;
|
|
1368
|
+
seen.add(m.$ref);
|
|
1369
|
+
const r = resolveRefSilent(m.$ref);
|
|
1370
|
+
return r ? constrains(r) : false;
|
|
1419
1371
|
});
|
|
1420
1372
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
1450
|
-
const strategy = schema.oneOf ? "one" : "any";
|
|
1451
|
-
const unionBase = {
|
|
1452
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1453
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1454
|
-
strategy
|
|
1455
|
-
};
|
|
1456
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1457
|
-
const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
|
|
1458
|
-
const sharedPropertiesNode = schema.properties ? parseSchema({
|
|
1459
|
-
schema: memberBaseSchema,
|
|
1460
|
-
name
|
|
1461
|
-
}, rawOptions) : void 0;
|
|
1462
|
-
if (sharedPropertiesNode || discriminator) {
|
|
1463
|
-
const members = unionMembers.map((s) => {
|
|
1464
|
-
const ref = isReference(s) ? s.$ref : void 0;
|
|
1465
|
-
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
|
|
1466
|
-
const memberNode = parseSchema({
|
|
1467
|
-
schema: s,
|
|
1468
|
-
name
|
|
1469
|
-
}, rawOptions);
|
|
1470
|
-
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1471
|
-
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
|
|
1472
|
-
propertyName: discriminator.propertyName,
|
|
1473
|
-
values: [discriminatorValue]
|
|
1474
|
-
})], { depth: "shallow" }), discriminator.propertyName) : void 0;
|
|
1475
|
-
return ast.factory.createSchema({
|
|
1476
|
-
type: "intersection",
|
|
1477
|
-
members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
|
|
1478
|
-
propertyName: discriminator.propertyName,
|
|
1479
|
-
value: discriminatorValue
|
|
1480
|
-
})]
|
|
1481
|
-
});
|
|
1482
|
-
});
|
|
1483
|
-
const unionNode = ast.factory.createSchema({
|
|
1484
|
-
type: "union",
|
|
1485
|
-
...unionBase,
|
|
1486
|
-
members
|
|
1487
|
-
});
|
|
1488
|
-
if (!sharedPropertiesNode) return unionNode;
|
|
1373
|
+
return constrains(variant) ? null : value;
|
|
1374
|
+
}
|
|
1375
|
+
const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
1376
|
+
const strategy = schema.oneOf ? "one" : "any";
|
|
1377
|
+
const unionBase = {
|
|
1378
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1379
|
+
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1380
|
+
strategy
|
|
1381
|
+
};
|
|
1382
|
+
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1383
|
+
const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
|
|
1384
|
+
const sharedPropertiesNode = schema.properties ? parse({
|
|
1385
|
+
schema: memberBaseSchema,
|
|
1386
|
+
name
|
|
1387
|
+
}, rawOptions) : void 0;
|
|
1388
|
+
if (sharedPropertiesNode || discriminator) {
|
|
1389
|
+
const members = unionMembers.map((s) => {
|
|
1390
|
+
const ref = isReference(s) ? s.$ref : void 0;
|
|
1391
|
+
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
|
|
1392
|
+
const memberNode = parse({
|
|
1393
|
+
schema: s,
|
|
1394
|
+
name
|
|
1395
|
+
}, rawOptions);
|
|
1396
|
+
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1397
|
+
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
|
|
1398
|
+
propertyName: discriminator.propertyName,
|
|
1399
|
+
values: [discriminatorValue]
|
|
1400
|
+
})], { depth: "shallow" }), discriminator.propertyName) : void 0;
|
|
1489
1401
|
return ast.factory.createSchema({
|
|
1490
1402
|
type: "intersection",
|
|
1491
|
-
|
|
1492
|
-
|
|
1403
|
+
members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
|
|
1404
|
+
propertyName: discriminator.propertyName,
|
|
1405
|
+
value: discriminatorValue
|
|
1406
|
+
})]
|
|
1493
1407
|
});
|
|
1494
|
-
}
|
|
1408
|
+
});
|
|
1495
1409
|
const unionNode = ast.factory.createSchema({
|
|
1496
1410
|
type: "union",
|
|
1497
1411
|
...unionBase,
|
|
1498
|
-
members
|
|
1499
|
-
schema: s,
|
|
1500
|
-
name
|
|
1501
|
-
}, rawOptions))
|
|
1412
|
+
members
|
|
1502
1413
|
});
|
|
1503
|
-
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
|
|
1507
|
-
*/
|
|
1508
|
-
function convertConst({ schema, name, nullable, defaultValue }) {
|
|
1509
|
-
const constValue = schema.const;
|
|
1510
|
-
if (constValue === null) return createNullSchema(schema, name);
|
|
1511
|
-
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1414
|
+
if (!sharedPropertiesNode) return unionNode;
|
|
1512
1415
|
return ast.factory.createSchema({
|
|
1513
|
-
type: "
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1416
|
+
type: "intersection",
|
|
1417
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1418
|
+
members: [unionNode, sharedPropertiesNode]
|
|
1517
1419
|
});
|
|
1518
1420
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1421
|
+
const unionNode = ast.factory.createSchema({
|
|
1422
|
+
type: "union",
|
|
1423
|
+
...unionBase,
|
|
1424
|
+
members: unionMembers.map((s) => parse({
|
|
1425
|
+
schema: s,
|
|
1426
|
+
name
|
|
1427
|
+
}, rawOptions))
|
|
1428
|
+
});
|
|
1429
|
+
return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: "shallow" });
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
|
|
1433
|
+
*/
|
|
1434
|
+
function convertConst({ schema, name, nullable, defaultValue }) {
|
|
1435
|
+
const constValue = schema.const;
|
|
1436
|
+
if (constValue === null) return createNullNode(schema, name);
|
|
1437
|
+
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1438
|
+
return ast.factory.createSchema({
|
|
1439
|
+
type: "enum",
|
|
1440
|
+
primitive: constPrimitive,
|
|
1441
|
+
enumValues: [constValue],
|
|
1442
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Converts a format-annotated schema into a special-type `SchemaNode`.
|
|
1447
|
+
* Returns `null` when the format should fall through to string handling (`dateType: false`).
|
|
1448
|
+
*/
|
|
1449
|
+
function convertFormat({ schema, name, nullable, defaultValue, options }) {
|
|
1450
|
+
const base = buildSchemaNode(schema, name, nullable, defaultValue);
|
|
1451
|
+
if (schema.format === "int64") return ast.factory.createSchema({
|
|
1452
|
+
type: options.integerType === "bigint" ? "bigint" : "integer",
|
|
1453
|
+
primitive: "integer",
|
|
1454
|
+
...base,
|
|
1455
|
+
min: schema.minimum,
|
|
1456
|
+
max: schema.maximum,
|
|
1457
|
+
exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
|
|
1458
|
+
exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
|
|
1459
|
+
});
|
|
1460
|
+
if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
|
|
1461
|
+
const dateType = getDateType(options, schema.format);
|
|
1462
|
+
if (!dateType) return null;
|
|
1463
|
+
if (dateType.type === "datetime") return ast.factory.createSchema({
|
|
1528
1464
|
...base,
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1465
|
+
primitive: "string",
|
|
1466
|
+
type: "datetime",
|
|
1467
|
+
offset: dateType.offset,
|
|
1468
|
+
local: dateType.local
|
|
1533
1469
|
});
|
|
1534
|
-
if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
|
|
1535
|
-
const dateType = getDateType(options, schema.format);
|
|
1536
|
-
if (!dateType) return null;
|
|
1537
|
-
if (dateType.type === "datetime") return ast.factory.createSchema({
|
|
1538
|
-
...base,
|
|
1539
|
-
primitive: "string",
|
|
1540
|
-
type: "datetime",
|
|
1541
|
-
offset: dateType.offset,
|
|
1542
|
-
local: dateType.local
|
|
1543
|
-
});
|
|
1544
|
-
return ast.factory.createSchema({
|
|
1545
|
-
...base,
|
|
1546
|
-
primitive: "string",
|
|
1547
|
-
type: dateType.type,
|
|
1548
|
-
representation: dateType.representation
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
const specialType = getSchemaType(schema.format);
|
|
1552
|
-
if (!specialType) return null;
|
|
1553
|
-
const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
|
|
1554
|
-
const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
|
|
1555
1470
|
return ast.factory.createSchema({
|
|
1556
1471
|
...base,
|
|
1557
|
-
primitive:
|
|
1558
|
-
type:
|
|
1559
|
-
|
|
1560
|
-
min: schema.minLength,
|
|
1561
|
-
max: schema.maxLength
|
|
1562
|
-
} : {}
|
|
1472
|
+
primitive: "string",
|
|
1473
|
+
type: dateType.type,
|
|
1474
|
+
representation: dateType.representation
|
|
1563
1475
|
});
|
|
1564
1476
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
}
|
|
1477
|
+
const specialType = getSchemaType(schema.format);
|
|
1478
|
+
if (!specialType) return null;
|
|
1479
|
+
const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
|
|
1480
|
+
const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
|
|
1481
|
+
return ast.factory.createSchema({
|
|
1482
|
+
...base,
|
|
1483
|
+
primitive: specialPrimitive,
|
|
1484
|
+
type: specialType,
|
|
1485
|
+
...hasLength ? {
|
|
1486
|
+
min: schema.minLength,
|
|
1487
|
+
max: schema.maxLength
|
|
1488
|
+
} : {}
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Converts an `enum` schema into an `EnumSchemaNode`.
|
|
1493
|
+
*/
|
|
1494
|
+
function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
|
|
1495
|
+
if (type === "array") return parse({
|
|
1496
|
+
schema: normalizeArrayEnum(schema),
|
|
1497
|
+
name
|
|
1498
|
+
}, rawOptions);
|
|
1499
|
+
const nullInEnum = schema.enum.includes(null);
|
|
1500
|
+
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1501
|
+
if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
|
|
1502
|
+
const enumNullable = nullable || nullInEnum || void 0;
|
|
1503
|
+
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1504
|
+
const enumPrimitive = getPrimitiveType(type);
|
|
1505
|
+
const enumBase = {
|
|
1506
|
+
type: "enum",
|
|
1507
|
+
primitive: enumPrimitive,
|
|
1508
|
+
...buildSchemaNode(schema, name, enumNullable, enumDefault)
|
|
1509
|
+
};
|
|
1510
|
+
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1511
|
+
const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
|
|
1512
|
+
if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
1513
|
+
const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
|
|
1514
|
+
const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
|
|
1515
|
+
const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
|
|
1516
|
+
const uniqueValues = [...new Set(filteredValues)];
|
|
1517
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
1607
1518
|
return ast.factory.createSchema({
|
|
1608
1519
|
...enumBase,
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
const schemaNode = nameEnums(parseSchema({
|
|
1621
|
-
schema: resolvedPropSchema,
|
|
1622
|
-
name: childName(name, propName)
|
|
1623
|
-
}, rawOptions), {
|
|
1624
|
-
parentName: name,
|
|
1625
|
-
propName,
|
|
1626
|
-
enumSuffix: options.enumSuffix
|
|
1627
|
-
});
|
|
1628
|
-
return ast.factory.createProperty({
|
|
1629
|
-
name: propName,
|
|
1630
|
-
schema: {
|
|
1631
|
-
...schemaNode,
|
|
1632
|
-
nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
|
|
1633
|
-
},
|
|
1634
|
-
required
|
|
1635
|
-
});
|
|
1636
|
-
}) : [];
|
|
1637
|
-
const additionalProperties = schema.additionalProperties;
|
|
1638
|
-
const additionalPropertiesNode = (() => {
|
|
1639
|
-
if (additionalProperties === true) return true;
|
|
1640
|
-
if (additionalProperties === false) return false;
|
|
1641
|
-
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1642
|
-
if (additionalProperties) return ast.factory.createSchema({ type: options.unknownType });
|
|
1643
|
-
})();
|
|
1644
|
-
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1645
|
-
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.factory.createSchema({ type: options.unknownType }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1646
|
-
const objectNode = ast.factory.createSchema({
|
|
1647
|
-
type: "object",
|
|
1648
|
-
primitive: "object",
|
|
1649
|
-
properties,
|
|
1650
|
-
additionalProperties: additionalPropertiesNode,
|
|
1651
|
-
patternProperties,
|
|
1652
|
-
minProperties: schema.minProperties,
|
|
1653
|
-
maxProperties: schema.maxProperties,
|
|
1654
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1655
|
-
});
|
|
1656
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1657
|
-
const discPropName = schema.discriminator.propertyName;
|
|
1658
|
-
const values = Object.keys(schema.discriminator.mapping);
|
|
1659
|
-
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
1660
|
-
return ast.applyMacros(objectNode, [macroDiscriminatorEnum({
|
|
1661
|
-
propertyName: discPropName,
|
|
1662
|
-
values,
|
|
1663
|
-
enumName
|
|
1664
|
-
})], { depth: "shallow" });
|
|
1665
|
-
}
|
|
1666
|
-
return objectNode;
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
1670
|
-
*/
|
|
1671
|
-
function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1672
|
-
const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
|
|
1673
|
-
const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: "any" }) : parseSchema({ schema: schema.items }, rawOptions);
|
|
1674
|
-
return ast.factory.createSchema({
|
|
1675
|
-
type: "tuple",
|
|
1676
|
-
primitive: "array",
|
|
1677
|
-
items: tupleItems,
|
|
1678
|
-
rest,
|
|
1679
|
-
min: schema.minItems,
|
|
1680
|
-
max: schema.maxItems,
|
|
1681
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1520
|
+
primitive: enumPrimitiveType,
|
|
1521
|
+
namedEnumValues: uniqueValues.map((value, index) => ({
|
|
1522
|
+
name: String(rawEnumNames?.[index] ?? value),
|
|
1523
|
+
value,
|
|
1524
|
+
primitive: enumPrimitiveType,
|
|
1525
|
+
description: rawEnumDescriptions?.[index]
|
|
1526
|
+
})).filter((entry) => {
|
|
1527
|
+
if (seenNames.has(entry.name)) return false;
|
|
1528
|
+
seenNames.add(entry.name);
|
|
1529
|
+
return true;
|
|
1530
|
+
})
|
|
1682
1531
|
});
|
|
1683
1532
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1533
|
+
return ast.factory.createSchema({
|
|
1534
|
+
...enumBase,
|
|
1535
|
+
enumValues: [...new Set(filteredValues)]
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Converts an object-like schema into an `ObjectSchemaNode`.
|
|
1540
|
+
*/
|
|
1541
|
+
function convertObject({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
|
|
1542
|
+
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1543
|
+
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1544
|
+
const resolvedPropSchema = propSchema;
|
|
1545
|
+
const propNullable = isNullable(resolvedPropSchema);
|
|
1546
|
+
const schemaNode = nameEnums(parse({
|
|
1547
|
+
schema: resolvedPropSchema,
|
|
1548
|
+
name: childName(name, propName)
|
|
1549
|
+
}, rawOptions), {
|
|
1550
|
+
parentName: name,
|
|
1551
|
+
propName,
|
|
1552
|
+
enumSuffix: options.enumSuffix
|
|
1702
1553
|
});
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
primitive: "string",
|
|
1711
|
-
min: schema.minLength,
|
|
1712
|
-
max: schema.maxLength,
|
|
1713
|
-
pattern: schema.pattern,
|
|
1714
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1554
|
+
return ast.factory.createProperty({
|
|
1555
|
+
name: propName,
|
|
1556
|
+
schema: {
|
|
1557
|
+
...schemaNode,
|
|
1558
|
+
nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
|
|
1559
|
+
},
|
|
1560
|
+
required
|
|
1715
1561
|
});
|
|
1562
|
+
}) : [];
|
|
1563
|
+
const additionalProperties = schema.additionalProperties;
|
|
1564
|
+
const additionalPropertiesNode = (() => {
|
|
1565
|
+
if (additionalProperties === true) return true;
|
|
1566
|
+
if (additionalProperties === false) return false;
|
|
1567
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parse({ schema: additionalProperties }, rawOptions);
|
|
1568
|
+
if (additionalProperties) return ast.factory.createSchema({ type: options.unknownType });
|
|
1569
|
+
})();
|
|
1570
|
+
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1571
|
+
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1572
|
+
const objectNode = ast.factory.createSchema({
|
|
1573
|
+
type: "object",
|
|
1574
|
+
primitive: "object",
|
|
1575
|
+
properties,
|
|
1576
|
+
additionalProperties: additionalPropertiesNode,
|
|
1577
|
+
patternProperties,
|
|
1578
|
+
minProperties: schema.minProperties,
|
|
1579
|
+
maxProperties: schema.maxProperties,
|
|
1580
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1581
|
+
});
|
|
1582
|
+
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1583
|
+
const discPropName = schema.discriminator.propertyName;
|
|
1584
|
+
const values = Object.keys(schema.discriminator.mapping);
|
|
1585
|
+
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
1586
|
+
return ast.applyMacros(objectNode, [macroDiscriminatorEnum({
|
|
1587
|
+
propertyName: discPropName,
|
|
1588
|
+
values,
|
|
1589
|
+
enumName
|
|
1590
|
+
})], { depth: "shallow" });
|
|
1716
1591
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1592
|
+
return objectNode;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
1596
|
+
*/
|
|
1597
|
+
function convertTuple({ schema, name, nullable, defaultValue, rawOptions, parse }) {
|
|
1598
|
+
const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }, rawOptions));
|
|
1599
|
+
const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: "any" }) : parse({ schema: schema.items }, rawOptions);
|
|
1600
|
+
return ast.factory.createSchema({
|
|
1601
|
+
type: "tuple",
|
|
1602
|
+
primitive: "array",
|
|
1603
|
+
items: tupleItems,
|
|
1604
|
+
rest,
|
|
1605
|
+
min: schema.minItems,
|
|
1606
|
+
max: schema.maxItems,
|
|
1607
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Converts a `type: 'array'` schema into an `ArraySchemaNode`.
|
|
1612
|
+
*/
|
|
1613
|
+
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
|
|
1614
|
+
const rawItems = schema.items;
|
|
1615
|
+
const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
|
|
1616
|
+
const items = rawItems ? [parse({
|
|
1617
|
+
schema: rawItems,
|
|
1618
|
+
name: itemName
|
|
1619
|
+
}, rawOptions)] : [];
|
|
1620
|
+
return ast.factory.createSchema({
|
|
1621
|
+
type: "array",
|
|
1622
|
+
primitive: "array",
|
|
1623
|
+
items,
|
|
1624
|
+
min: schema.minItems,
|
|
1625
|
+
max: schema.maxItems,
|
|
1626
|
+
unique: schema.uniqueItems ?? void 0,
|
|
1627
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
1632
|
+
*/
|
|
1633
|
+
function convertString({ schema, name, nullable, defaultValue }) {
|
|
1634
|
+
return ast.factory.createSchema({
|
|
1635
|
+
type: "string",
|
|
1636
|
+
primitive: "string",
|
|
1637
|
+
min: schema.minLength,
|
|
1638
|
+
max: schema.maxLength,
|
|
1639
|
+
pattern: schema.pattern,
|
|
1640
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Converts a `type: 'number'` or `type: 'integer'` schema.
|
|
1645
|
+
*/
|
|
1646
|
+
function convertNumeric({ schema, name, nullable, defaultValue }, type) {
|
|
1647
|
+
return ast.factory.createSchema({
|
|
1648
|
+
type,
|
|
1649
|
+
primitive: type,
|
|
1650
|
+
min: schema.minimum,
|
|
1651
|
+
max: schema.maximum,
|
|
1652
|
+
exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
|
|
1653
|
+
exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
|
|
1654
|
+
multipleOf: schema.multipleOf,
|
|
1655
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Converts a `type: 'boolean'` schema.
|
|
1660
|
+
*/
|
|
1661
|
+
function convertBoolean({ schema, name, nullable, defaultValue }) {
|
|
1662
|
+
return ast.factory.createSchema({
|
|
1663
|
+
type: "boolean",
|
|
1664
|
+
primitive: "boolean",
|
|
1665
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
1670
|
+
* into a `blob` node.
|
|
1671
|
+
*/
|
|
1672
|
+
function convertBinary({ schema, name, nullable, defaultValue }) {
|
|
1673
|
+
return ast.factory.createSchema({
|
|
1674
|
+
type: "blob",
|
|
1675
|
+
primitive: "string",
|
|
1676
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
1681
|
+
*
|
|
1682
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parse`
|
|
1683
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
1684
|
+
*/
|
|
1685
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, parse }) {
|
|
1686
|
+
const types = schema.type;
|
|
1687
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
1688
|
+
if (nonNullTypes.length <= 1) return null;
|
|
1689
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
1690
|
+
return ast.factory.createSchema({
|
|
1691
|
+
type: "union",
|
|
1692
|
+
members: nonNullTypes.map((t) => {
|
|
1693
|
+
return parse({
|
|
1694
|
+
schema: {
|
|
1695
|
+
...schema,
|
|
1696
|
+
type: t
|
|
1697
|
+
},
|
|
1698
|
+
name
|
|
1699
|
+
}, rawOptions);
|
|
1700
|
+
}),
|
|
1701
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1706
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1707
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1708
|
+
* match/convert/fall-through contract.
|
|
1709
|
+
*/
|
|
1710
|
+
const schemaRules = [
|
|
1711
|
+
{
|
|
1712
|
+
match: ({ schema }) => isReference(schema),
|
|
1713
|
+
convert: convertRef
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
1717
|
+
convert: convertAllOf
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
1721
|
+
convert: convertUnion
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1725
|
+
convert: convertConst
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
match: ({ schema }) => !!schema.format,
|
|
1729
|
+
convert: convertFormat
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
match: ({ schema }) => isBinary(schema),
|
|
1733
|
+
convert: convertBinary
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1737
|
+
convert: convertMultiType
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1741
|
+
convert: convertString
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1745
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
1749
|
+
convert: convertEnum
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1753
|
+
convert: convertObject
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
1757
|
+
convert: convertTuple
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1761
|
+
convert: convertArray
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
match: ({ type }) => type === "string",
|
|
1765
|
+
convert: convertString
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
match: ({ type }) => type === "number",
|
|
1769
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
match: ({ type }) => type === "integer",
|
|
1773
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
match: ({ type }) => type === "boolean",
|
|
1777
|
+
convert: convertBoolean
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
match: ({ type }) => type === "null",
|
|
1781
|
+
convert: ({ schema, name, nullable }) => createNullNode(schema, name, nullable)
|
|
1731
1782
|
}
|
|
1783
|
+
];
|
|
1784
|
+
//#endregion
|
|
1785
|
+
//#region src/parser.ts
|
|
1786
|
+
/**
|
|
1787
|
+
* Creates the schema and operation converters bound to one OpenAPI document.
|
|
1788
|
+
*
|
|
1789
|
+
* Owns the per-instance `$ref` state (cycle detection, resolved-node cache, existence cache) and
|
|
1790
|
+
* the `parseSchema` recursion seam, then dispatches each schema through the ordered `schemaRules`
|
|
1791
|
+
* table from `converters.ts`. Every converter is a standalone function that recurses through the
|
|
1792
|
+
* `parse` function passed to it, so this file only wires state to the converters.
|
|
1793
|
+
*
|
|
1794
|
+
* @internal
|
|
1795
|
+
*/
|
|
1796
|
+
function createSchemaParser(ctx) {
|
|
1797
|
+
const document = ctx.document;
|
|
1732
1798
|
/**
|
|
1733
|
-
*
|
|
1799
|
+
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
1800
|
+
* recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
|
|
1734
1801
|
*/
|
|
1735
|
-
|
|
1736
|
-
return ast.factory.createSchema({
|
|
1737
|
-
type: "boolean",
|
|
1738
|
-
primitive: "boolean",
|
|
1739
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1802
|
+
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1742
1803
|
/**
|
|
1743
|
-
*
|
|
1744
|
-
*
|
|
1804
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1805
|
+
*
|
|
1806
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1807
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1808
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1809
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1745
1810
|
*/
|
|
1746
|
-
|
|
1747
|
-
return ast.factory.createSchema({
|
|
1748
|
-
type: "blob",
|
|
1749
|
-
primitive: "string",
|
|
1750
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1811
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1753
1812
|
/**
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* falls through and handles it as that single type with nullability already folded in.
|
|
1813
|
+
* Memoized record of whether a `$ref` path resolves to a node the document actually defines.
|
|
1814
|
+
* A circular ref still resolves to an existing target, so this stays `true` for cycles and only
|
|
1815
|
+
* goes `false` for a `$ref` that points at a component the spec never declares.
|
|
1758
1816
|
*/
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
name
|
|
1772
|
-
}, rawOptions)),
|
|
1773
|
-
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1774
|
-
});
|
|
1817
|
+
const refExistence = /* @__PURE__ */ new Map();
|
|
1818
|
+
function refExists(refPath) {
|
|
1819
|
+
if (!refExistence.has(refPath)) {
|
|
1820
|
+
let exists = false;
|
|
1821
|
+
try {
|
|
1822
|
+
exists = !!resolveRef(document, refPath);
|
|
1823
|
+
} catch {
|
|
1824
|
+
exists = false;
|
|
1825
|
+
}
|
|
1826
|
+
refExistence.set(refPath, exists);
|
|
1827
|
+
}
|
|
1828
|
+
return refExistence.get(refPath) ?? false;
|
|
1775
1829
|
}
|
|
1776
1830
|
/**
|
|
1777
|
-
*
|
|
1778
|
-
* `
|
|
1779
|
-
*
|
|
1780
|
-
* match/convert/fall-through contract.
|
|
1831
|
+
* Resolves a `$ref` to its parsed node, guarding against cycles and memoizing per instance.
|
|
1832
|
+
* Returns `null` when the ref is currently being resolved (a cycle) or cannot be resolved
|
|
1833
|
+
* (e.g. a minimal document in a unit test).
|
|
1781
1834
|
*/
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
{
|
|
1796
|
-
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1797
|
-
convert: convertConst
|
|
1798
|
-
},
|
|
1799
|
-
{
|
|
1800
|
-
match: ({ schema }) => !!schema.format,
|
|
1801
|
-
convert: convertFormat
|
|
1802
|
-
},
|
|
1803
|
-
{
|
|
1804
|
-
match: ({ schema }) => isBinary(schema),
|
|
1805
|
-
convert: convertBlob
|
|
1806
|
-
},
|
|
1807
|
-
{
|
|
1808
|
-
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1809
|
-
convert: convertMultiType
|
|
1810
|
-
},
|
|
1811
|
-
{
|
|
1812
|
-
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1813
|
-
convert: convertString
|
|
1814
|
-
},
|
|
1815
|
-
{
|
|
1816
|
-
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1817
|
-
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1818
|
-
},
|
|
1819
|
-
{
|
|
1820
|
-
match: ({ schema }) => !!schema.enum?.length,
|
|
1821
|
-
convert: convertEnum
|
|
1822
|
-
},
|
|
1823
|
-
{
|
|
1824
|
-
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1825
|
-
convert: convertObject
|
|
1826
|
-
},
|
|
1827
|
-
{
|
|
1828
|
-
match: ({ schema }) => "prefixItems" in schema,
|
|
1829
|
-
convert: convertTuple
|
|
1830
|
-
},
|
|
1831
|
-
{
|
|
1832
|
-
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1833
|
-
convert: convertArray
|
|
1834
|
-
},
|
|
1835
|
-
{
|
|
1836
|
-
match: ({ type }) => type === "string",
|
|
1837
|
-
convert: convertString
|
|
1838
|
-
},
|
|
1839
|
-
{
|
|
1840
|
-
match: ({ type }) => type === "number",
|
|
1841
|
-
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1842
|
-
},
|
|
1843
|
-
{
|
|
1844
|
-
match: ({ type }) => type === "integer",
|
|
1845
|
-
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1846
|
-
},
|
|
1847
|
-
{
|
|
1848
|
-
match: ({ type }) => type === "boolean",
|
|
1849
|
-
convert: convertBoolean
|
|
1850
|
-
},
|
|
1851
|
-
{
|
|
1852
|
-
match: ({ type }) => type === "null",
|
|
1853
|
-
convert: ({ schema, name, nullable }) => createNullSchema(schema, name, nullable)
|
|
1835
|
+
function resolveRefNode(refPath, rawOptions) {
|
|
1836
|
+
if (resolvingRefs.has(refPath)) return null;
|
|
1837
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1838
|
+
let resolved = null;
|
|
1839
|
+
try {
|
|
1840
|
+
const referenced = resolveRef(document, refPath);
|
|
1841
|
+
if (referenced) {
|
|
1842
|
+
resolvingRefs.add(refPath);
|
|
1843
|
+
resolved = parseSchema({ schema: referenced }, rawOptions);
|
|
1844
|
+
resolvingRefs.delete(refPath);
|
|
1845
|
+
}
|
|
1846
|
+
} catch {}
|
|
1847
|
+
resolvedRefCache.set(refPath, resolved);
|
|
1854
1848
|
}
|
|
1855
|
-
|
|
1849
|
+
return resolvedRefCache.get(refPath) ?? null;
|
|
1850
|
+
}
|
|
1856
1851
|
/**
|
|
1857
1852
|
* Converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1858
1853
|
*
|
|
@@ -1871,18 +1866,22 @@ function createSchemaParser(ctx) {
|
|
|
1871
1866
|
name
|
|
1872
1867
|
}, rawOptions);
|
|
1873
1868
|
const nullable = isNullable(schema) || void 0;
|
|
1874
|
-
const
|
|
1869
|
+
const context = {
|
|
1875
1870
|
schema,
|
|
1876
1871
|
name,
|
|
1877
1872
|
nullable,
|
|
1878
1873
|
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
1879
1874
|
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1880
1875
|
rawOptions,
|
|
1881
|
-
options
|
|
1876
|
+
options,
|
|
1877
|
+
parse: parseSchema,
|
|
1878
|
+
document,
|
|
1879
|
+
resolveRefNode,
|
|
1880
|
+
refExists
|
|
1882
1881
|
};
|
|
1883
1882
|
for (const rule of schemaRules) {
|
|
1884
|
-
if (!rule.match(
|
|
1885
|
-
const node = rule.convert(
|
|
1883
|
+
if (!rule.match(context)) continue;
|
|
1884
|
+
const node = rule.convert(context);
|
|
1886
1885
|
if (node) return node;
|
|
1887
1886
|
}
|
|
1888
1887
|
const emptyType = options.emptySchemaType;
|