@kubb/adapter-oas 5.0.0-beta.93 → 5.0.0-beta.95
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 +663 -671
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -21
- package/dist/index.js +664 -672
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
-
import { ast, childName,
|
|
2
|
+
import { ast, childName, enumPropName, extractRefName, findCircularSchemas, macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion, mergeAdjacentObjectsLazy, narrowSchema } from "@kubb/ast";
|
|
3
3
|
import { Diagnostics, createAdapter } from "@kubb/core";
|
|
4
4
|
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;
|
|
@@ -1035,7 +1014,7 @@ function resolveSchemaRef(document, schema) {
|
|
|
1035
1014
|
*
|
|
1036
1015
|
* @example
|
|
1037
1016
|
* ```ts
|
|
1038
|
-
* const { schemas,
|
|
1017
|
+
* const { schemas, renames } = getSchemas(document, { contentType: 'application/json' })
|
|
1039
1018
|
* ```
|
|
1040
1019
|
*/
|
|
1041
1020
|
function getSchemas(document, { contentType }) {
|
|
@@ -1060,7 +1039,7 @@ function getSchemas(document, { contentType }) {
|
|
|
1060
1039
|
normalizedNames.set(key, bucket);
|
|
1061
1040
|
}
|
|
1062
1041
|
const schemas = {};
|
|
1063
|
-
const
|
|
1042
|
+
const renames = /* @__PURE__ */ new Map();
|
|
1064
1043
|
for (const [, items] of normalizedNames) {
|
|
1065
1044
|
const isSingle = items.length === 1;
|
|
1066
1045
|
let hasMultipleSources = false;
|
|
@@ -1075,12 +1054,12 @@ function getSchemas(document, { contentType }) {
|
|
|
1075
1054
|
const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index === 0 ? "" : String(index + 1);
|
|
1076
1055
|
const uniqueName = item.originalName + suffix;
|
|
1077
1056
|
schemas[uniqueName] = item.schema;
|
|
1078
|
-
|
|
1057
|
+
if (suffix) renames.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
|
|
1079
1058
|
});
|
|
1080
1059
|
}
|
|
1081
1060
|
return {
|
|
1082
1061
|
schemas: sortSchemas(schemas),
|
|
1083
|
-
|
|
1062
|
+
renames
|
|
1084
1063
|
};
|
|
1085
1064
|
}
|
|
1086
1065
|
/**
|
|
@@ -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,638 @@ 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, renames }) {
|
|
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
|
+
const targetName = renames?.get(schema.$ref);
|
|
1236
|
+
return ast.factory.createSchema({
|
|
1237
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1238
|
+
type: "ref",
|
|
1239
|
+
name: extractRefName(schema.$ref),
|
|
1240
|
+
ref: schema.$ref,
|
|
1241
|
+
...targetName ? { targetName } : {},
|
|
1242
|
+
schema: resolvedSchema
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
|
|
1249
1247
|
*/
|
|
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
|
-
});
|
|
1248
|
+
function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
|
|
1249
|
+
if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
|
|
1250
|
+
const [memberSchema] = schema.allOf;
|
|
1251
|
+
const memberNode = parse({
|
|
1252
|
+
schema: memberSchema,
|
|
1253
|
+
name
|
|
1254
|
+
}, rawOptions);
|
|
1255
|
+
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1256
|
+
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
1257
|
+
const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
|
|
1313
1258
|
return ast.factory.createSchema({
|
|
1314
|
-
...
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1259
|
+
...memberNodeProps,
|
|
1260
|
+
name,
|
|
1261
|
+
title: schema.title ?? memberNode.title,
|
|
1262
|
+
description: schema.description ?? memberNode.description,
|
|
1263
|
+
deprecated: schema.deprecated ?? memberNode.deprecated,
|
|
1264
|
+
nullable: mergedNullable,
|
|
1265
|
+
readOnly: schema.readOnly ?? memberNode.readOnly,
|
|
1266
|
+
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1267
|
+
default: mergedDefault,
|
|
1268
|
+
examples: extractExamples(schema) ?? memberNode.examples,
|
|
1269
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1270
|
+
format: schema.format ?? memberNode.format
|
|
1319
1271
|
});
|
|
1320
1272
|
}
|
|
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
|
|
1273
|
+
const filteredDiscriminantValues = [];
|
|
1274
|
+
const allOfMembers = schema.allOf.filter((item) => {
|
|
1275
|
+
if (!isReference(item) || !name) return true;
|
|
1276
|
+
const deref = resolveRef(document, item.$ref);
|
|
1277
|
+
if (!deref || !isDiscriminator(deref)) return true;
|
|
1278
|
+
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1279
|
+
if (!parentUnion) return true;
|
|
1280
|
+
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1281
|
+
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1282
|
+
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1283
|
+
if (inOneOf || inMapping) {
|
|
1284
|
+
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
|
|
1285
|
+
if (discriminatorValue) filteredDiscriminantValues.push({
|
|
1286
|
+
propertyName: deref.discriminator.propertyName,
|
|
1287
|
+
value: discriminatorValue
|
|
1347
1288
|
});
|
|
1289
|
+
return false;
|
|
1348
1290
|
}
|
|
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
|
-
},
|
|
1291
|
+
return true;
|
|
1292
|
+
}).map((s) => parse({
|
|
1293
|
+
schema: s,
|
|
1294
|
+
name
|
|
1295
|
+
}, rawOptions));
|
|
1296
|
+
const syntheticStart = allOfMembers.length;
|
|
1297
|
+
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1298
|
+
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1299
|
+
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1300
|
+
if (missingRequired.length) {
|
|
1301
|
+
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1302
|
+
if (!isReference(item)) return [item];
|
|
1303
|
+
const deref = resolveRef(document, item.$ref);
|
|
1304
|
+
return deref && !isReference(deref) ? [deref] : [];
|
|
1305
|
+
});
|
|
1306
|
+
for (const key of missingRequired) for (const resolved of resolvedMembers) {
|
|
1307
|
+
const prop = resolved.properties?.[key];
|
|
1308
|
+
if (prop) {
|
|
1309
|
+
const memberSchema = {
|
|
1310
|
+
properties: { [key]: prop },
|
|
1311
|
+
required: [key]
|
|
1312
|
+
};
|
|
1313
|
+
allOfMembers.push(parse({
|
|
1314
|
+
schema: memberSchema,
|
|
1388
1315
|
name
|
|
1389
1316
|
}, rawOptions));
|
|
1390
1317
|
break;
|
|
1391
1318
|
}
|
|
1392
1319
|
}
|
|
1393
1320
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1321
|
+
}
|
|
1322
|
+
if (schema.properties) {
|
|
1323
|
+
const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
|
|
1324
|
+
allOfMembers.push(parse({ schema: schemaWithoutAllOf }, rawOptions));
|
|
1325
|
+
}
|
|
1326
|
+
for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
|
|
1327
|
+
propertyName,
|
|
1328
|
+
value
|
|
1329
|
+
}));
|
|
1330
|
+
return ast.factory.createSchema({
|
|
1331
|
+
type: "intersection",
|
|
1332
|
+
members: [...mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1333
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
|
|
1338
|
+
*/
|
|
1339
|
+
function convertUnion({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
|
|
1340
|
+
function pickDiscriminatorPropertyNode(node, propertyName) {
|
|
1341
|
+
const discriminatorProperty = ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
|
|
1342
|
+
if (!discriminatorProperty) return null;
|
|
1402
1343
|
return ast.factory.createSchema({
|
|
1403
|
-
type: "
|
|
1404
|
-
|
|
1405
|
-
|
|
1344
|
+
type: "object",
|
|
1345
|
+
primitive: "object",
|
|
1346
|
+
properties: [discriminatorProperty]
|
|
1406
1347
|
});
|
|
1407
1348
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1349
|
+
function resolveRefSilent($ref) {
|
|
1350
|
+
if (!$ref.startsWith("#")) return null;
|
|
1351
|
+
return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
|
|
1352
|
+
}
|
|
1353
|
+
function implicitDiscriminantValue(member) {
|
|
1354
|
+
if (!discriminator || discriminator.mapping || !isReference(member)) return null;
|
|
1355
|
+
const value = extractRefName(member.$ref);
|
|
1356
|
+
if (!value) return null;
|
|
1357
|
+
const variant = resolveRefSilent(member.$ref);
|
|
1358
|
+
if (!variant) return null;
|
|
1359
|
+
const propertyName = discriminator.propertyName;
|
|
1360
|
+
const seen = /* @__PURE__ */ new Set([member.$ref]);
|
|
1361
|
+
function constrains(v) {
|
|
1362
|
+
const prop = v.properties?.[propertyName];
|
|
1363
|
+
const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
|
|
1364
|
+
if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
|
|
1365
|
+
const composition = v.allOf ?? v.oneOf ?? v.anyOf;
|
|
1366
|
+
if (!composition) return false;
|
|
1367
|
+
return composition.some((m) => {
|
|
1368
|
+
if (!isReference(m)) return constrains(m);
|
|
1369
|
+
if (seen.has(m.$ref)) return false;
|
|
1370
|
+
seen.add(m.$ref);
|
|
1371
|
+
const r = resolveRefSilent(m.$ref);
|
|
1372
|
+
return r ? constrains(r) : false;
|
|
1419
1373
|
});
|
|
1420
1374
|
}
|
|
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;
|
|
1375
|
+
return constrains(variant) ? null : value;
|
|
1376
|
+
}
|
|
1377
|
+
const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
1378
|
+
const strategy = schema.oneOf ? "one" : "any";
|
|
1379
|
+
const unionBase = {
|
|
1380
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1381
|
+
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1382
|
+
strategy
|
|
1383
|
+
};
|
|
1384
|
+
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1385
|
+
const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
|
|
1386
|
+
const sharedPropertiesNode = schema.properties ? parse({
|
|
1387
|
+
schema: memberBaseSchema,
|
|
1388
|
+
name
|
|
1389
|
+
}, rawOptions) : void 0;
|
|
1390
|
+
if (sharedPropertiesNode || discriminator) {
|
|
1391
|
+
const members = unionMembers.map((s) => {
|
|
1392
|
+
const ref = isReference(s) ? s.$ref : void 0;
|
|
1393
|
+
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
|
|
1394
|
+
const memberNode = parse({
|
|
1395
|
+
schema: s,
|
|
1396
|
+
name
|
|
1397
|
+
}, rawOptions);
|
|
1398
|
+
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1399
|
+
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
|
|
1400
|
+
propertyName: discriminator.propertyName,
|
|
1401
|
+
values: [discriminatorValue]
|
|
1402
|
+
})], { depth: "shallow" }), discriminator.propertyName) : void 0;
|
|
1489
1403
|
return ast.factory.createSchema({
|
|
1490
1404
|
type: "intersection",
|
|
1491
|
-
|
|
1492
|
-
|
|
1405
|
+
members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
|
|
1406
|
+
propertyName: discriminator.propertyName,
|
|
1407
|
+
value: discriminatorValue
|
|
1408
|
+
})]
|
|
1493
1409
|
});
|
|
1494
|
-
}
|
|
1410
|
+
});
|
|
1495
1411
|
const unionNode = ast.factory.createSchema({
|
|
1496
1412
|
type: "union",
|
|
1497
1413
|
...unionBase,
|
|
1498
|
-
members
|
|
1499
|
-
schema: s,
|
|
1500
|
-
name
|
|
1501
|
-
}, rawOptions))
|
|
1414
|
+
members
|
|
1502
1415
|
});
|
|
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");
|
|
1416
|
+
if (!sharedPropertiesNode) return unionNode;
|
|
1512
1417
|
return ast.factory.createSchema({
|
|
1513
|
-
type: "
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1418
|
+
type: "intersection",
|
|
1419
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1420
|
+
members: [unionNode, sharedPropertiesNode]
|
|
1517
1421
|
});
|
|
1518
1422
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1423
|
+
const unionNode = ast.factory.createSchema({
|
|
1424
|
+
type: "union",
|
|
1425
|
+
...unionBase,
|
|
1426
|
+
members: unionMembers.map((s) => parse({
|
|
1427
|
+
schema: s,
|
|
1428
|
+
name
|
|
1429
|
+
}, rawOptions))
|
|
1430
|
+
});
|
|
1431
|
+
return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: "shallow" });
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
|
|
1435
|
+
*/
|
|
1436
|
+
function convertConst({ schema, name, nullable, defaultValue }) {
|
|
1437
|
+
const constValue = schema.const;
|
|
1438
|
+
if (constValue === null) return createNullNode(schema, name);
|
|
1439
|
+
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1440
|
+
return ast.factory.createSchema({
|
|
1441
|
+
type: "enum",
|
|
1442
|
+
primitive: constPrimitive,
|
|
1443
|
+
enumValues: [constValue],
|
|
1444
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Converts a format-annotated schema into a special-type `SchemaNode`.
|
|
1449
|
+
* Returns `null` when the format should fall through to string handling (`dateType: false`).
|
|
1450
|
+
*/
|
|
1451
|
+
function convertFormat({ schema, name, nullable, defaultValue, options }) {
|
|
1452
|
+
const base = buildSchemaNode(schema, name, nullable, defaultValue);
|
|
1453
|
+
if (schema.format === "int64") return ast.factory.createSchema({
|
|
1454
|
+
type: options.integerType === "bigint" ? "bigint" : "integer",
|
|
1455
|
+
primitive: "integer",
|
|
1456
|
+
...base,
|
|
1457
|
+
min: schema.minimum,
|
|
1458
|
+
max: schema.maximum,
|
|
1459
|
+
exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
|
|
1460
|
+
exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
|
|
1461
|
+
});
|
|
1462
|
+
if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
|
|
1463
|
+
const dateType = getDateType(options, schema.format);
|
|
1464
|
+
if (!dateType) return null;
|
|
1465
|
+
if (dateType.type === "datetime") return ast.factory.createSchema({
|
|
1528
1466
|
...base,
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1467
|
+
primitive: "string",
|
|
1468
|
+
type: "datetime",
|
|
1469
|
+
offset: dateType.offset,
|
|
1470
|
+
local: dateType.local
|
|
1533
1471
|
});
|
|
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
1472
|
return ast.factory.createSchema({
|
|
1556
1473
|
...base,
|
|
1557
|
-
primitive:
|
|
1558
|
-
type:
|
|
1559
|
-
|
|
1560
|
-
min: schema.minLength,
|
|
1561
|
-
max: schema.maxLength
|
|
1562
|
-
} : {}
|
|
1474
|
+
primitive: "string",
|
|
1475
|
+
type: dateType.type,
|
|
1476
|
+
representation: dateType.representation
|
|
1563
1477
|
});
|
|
1564
1478
|
}
|
|
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
|
-
}
|
|
1479
|
+
const specialType = getSchemaType(schema.format);
|
|
1480
|
+
if (!specialType) return null;
|
|
1481
|
+
const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
|
|
1482
|
+
const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
|
|
1483
|
+
return ast.factory.createSchema({
|
|
1484
|
+
...base,
|
|
1485
|
+
primitive: specialPrimitive,
|
|
1486
|
+
type: specialType,
|
|
1487
|
+
...hasLength ? {
|
|
1488
|
+
min: schema.minLength,
|
|
1489
|
+
max: schema.maxLength
|
|
1490
|
+
} : {}
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Converts an `enum` schema into an `EnumSchemaNode`.
|
|
1495
|
+
*/
|
|
1496
|
+
function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
|
|
1497
|
+
if (type === "array") return parse({
|
|
1498
|
+
schema: normalizeArrayEnum(schema),
|
|
1499
|
+
name
|
|
1500
|
+
}, rawOptions);
|
|
1501
|
+
const nullInEnum = schema.enum.includes(null);
|
|
1502
|
+
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1503
|
+
if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
|
|
1504
|
+
const enumNullable = nullable || nullInEnum || void 0;
|
|
1505
|
+
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1506
|
+
const enumPrimitive = getPrimitiveType(type);
|
|
1507
|
+
const enumBase = {
|
|
1508
|
+
type: "enum",
|
|
1509
|
+
primitive: enumPrimitive,
|
|
1510
|
+
...buildSchemaNode(schema, name, enumNullable, enumDefault)
|
|
1511
|
+
};
|
|
1512
|
+
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1513
|
+
const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
|
|
1514
|
+
if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
1515
|
+
const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
|
|
1516
|
+
const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
|
|
1517
|
+
const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
|
|
1518
|
+
const uniqueValues = [...new Set(filteredValues)];
|
|
1519
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
1607
1520
|
return ast.factory.createSchema({
|
|
1608
1521
|
...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)
|
|
1522
|
+
primitive: enumPrimitiveType,
|
|
1523
|
+
namedEnumValues: uniqueValues.map((value, index) => ({
|
|
1524
|
+
name: String(rawEnumNames?.[index] ?? value),
|
|
1525
|
+
value,
|
|
1526
|
+
primitive: enumPrimitiveType,
|
|
1527
|
+
description: rawEnumDescriptions?.[index]
|
|
1528
|
+
})).filter((entry) => {
|
|
1529
|
+
if (seenNames.has(entry.name)) return false;
|
|
1530
|
+
seenNames.add(entry.name);
|
|
1531
|
+
return true;
|
|
1532
|
+
})
|
|
1682
1533
|
});
|
|
1683
1534
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1535
|
+
return ast.factory.createSchema({
|
|
1536
|
+
...enumBase,
|
|
1537
|
+
enumValues: [...new Set(filteredValues)]
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Converts an object-like schema into an `ObjectSchemaNode`.
|
|
1542
|
+
*/
|
|
1543
|
+
function convertObject({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
|
|
1544
|
+
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1545
|
+
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1546
|
+
const resolvedPropSchema = propSchema;
|
|
1547
|
+
const propNullable = isNullable(resolvedPropSchema);
|
|
1548
|
+
const schemaNode = nameEnums(parse({
|
|
1549
|
+
schema: resolvedPropSchema,
|
|
1550
|
+
name: childName(name, propName)
|
|
1551
|
+
}, rawOptions), {
|
|
1552
|
+
parentName: name,
|
|
1553
|
+
propName,
|
|
1554
|
+
enumSuffix: options.enumSuffix
|
|
1702
1555
|
});
|
|
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)
|
|
1556
|
+
return ast.factory.createProperty({
|
|
1557
|
+
name: propName,
|
|
1558
|
+
schema: {
|
|
1559
|
+
...schemaNode,
|
|
1560
|
+
nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
|
|
1561
|
+
},
|
|
1562
|
+
required
|
|
1715
1563
|
});
|
|
1564
|
+
}) : [];
|
|
1565
|
+
const additionalProperties = schema.additionalProperties;
|
|
1566
|
+
const additionalPropertiesNode = (() => {
|
|
1567
|
+
if (additionalProperties === true) return true;
|
|
1568
|
+
if (additionalProperties === false) return false;
|
|
1569
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parse({ schema: additionalProperties }, rawOptions);
|
|
1570
|
+
if (additionalProperties) return ast.factory.createSchema({ type: options.unknownType });
|
|
1571
|
+
})();
|
|
1572
|
+
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1573
|
+
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;
|
|
1574
|
+
const objectNode = ast.factory.createSchema({
|
|
1575
|
+
type: "object",
|
|
1576
|
+
primitive: "object",
|
|
1577
|
+
properties,
|
|
1578
|
+
additionalProperties: additionalPropertiesNode,
|
|
1579
|
+
patternProperties,
|
|
1580
|
+
minProperties: schema.minProperties,
|
|
1581
|
+
maxProperties: schema.maxProperties,
|
|
1582
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1583
|
+
});
|
|
1584
|
+
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1585
|
+
const discPropName = schema.discriminator.propertyName;
|
|
1586
|
+
const values = Object.keys(schema.discriminator.mapping);
|
|
1587
|
+
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
1588
|
+
return ast.applyMacros(objectNode, [macroDiscriminatorEnum({
|
|
1589
|
+
propertyName: discPropName,
|
|
1590
|
+
values,
|
|
1591
|
+
enumName
|
|
1592
|
+
})], { depth: "shallow" });
|
|
1716
1593
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1594
|
+
return objectNode;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
1598
|
+
*/
|
|
1599
|
+
function convertTuple({ schema, name, nullable, defaultValue, rawOptions, parse }) {
|
|
1600
|
+
const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }, rawOptions));
|
|
1601
|
+
const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: "any" }) : parse({ schema: schema.items }, rawOptions);
|
|
1602
|
+
return ast.factory.createSchema({
|
|
1603
|
+
type: "tuple",
|
|
1604
|
+
primitive: "array",
|
|
1605
|
+
items: tupleItems,
|
|
1606
|
+
rest,
|
|
1607
|
+
min: schema.minItems,
|
|
1608
|
+
max: schema.maxItems,
|
|
1609
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Converts a `type: 'array'` schema into an `ArraySchemaNode`.
|
|
1614
|
+
*/
|
|
1615
|
+
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
|
|
1616
|
+
const rawItems = schema.items;
|
|
1617
|
+
const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
|
|
1618
|
+
const items = rawItems ? [parse({
|
|
1619
|
+
schema: rawItems,
|
|
1620
|
+
name: itemName
|
|
1621
|
+
}, rawOptions)] : [];
|
|
1622
|
+
return ast.factory.createSchema({
|
|
1623
|
+
type: "array",
|
|
1624
|
+
primitive: "array",
|
|
1625
|
+
items,
|
|
1626
|
+
min: schema.minItems,
|
|
1627
|
+
max: schema.maxItems,
|
|
1628
|
+
unique: schema.uniqueItems ?? void 0,
|
|
1629
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
1634
|
+
*/
|
|
1635
|
+
function convertString({ schema, name, nullable, defaultValue }) {
|
|
1636
|
+
return ast.factory.createSchema({
|
|
1637
|
+
type: "string",
|
|
1638
|
+
primitive: "string",
|
|
1639
|
+
min: schema.minLength,
|
|
1640
|
+
max: schema.maxLength,
|
|
1641
|
+
pattern: schema.pattern,
|
|
1642
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Converts a `type: 'number'` or `type: 'integer'` schema.
|
|
1647
|
+
*/
|
|
1648
|
+
function convertNumeric({ schema, name, nullable, defaultValue }, type) {
|
|
1649
|
+
return ast.factory.createSchema({
|
|
1650
|
+
type,
|
|
1651
|
+
primitive: type,
|
|
1652
|
+
min: schema.minimum,
|
|
1653
|
+
max: schema.maximum,
|
|
1654
|
+
exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
|
|
1655
|
+
exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
|
|
1656
|
+
multipleOf: schema.multipleOf,
|
|
1657
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Converts a `type: 'boolean'` schema.
|
|
1662
|
+
*/
|
|
1663
|
+
function convertBoolean({ schema, name, nullable, defaultValue }) {
|
|
1664
|
+
return ast.factory.createSchema({
|
|
1665
|
+
type: "boolean",
|
|
1666
|
+
primitive: "boolean",
|
|
1667
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
1672
|
+
* into a `blob` node.
|
|
1673
|
+
*/
|
|
1674
|
+
function convertBinary({ schema, name, nullable, defaultValue }) {
|
|
1675
|
+
return ast.factory.createSchema({
|
|
1676
|
+
type: "blob",
|
|
1677
|
+
primitive: "string",
|
|
1678
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
1683
|
+
*
|
|
1684
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parse`
|
|
1685
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
1686
|
+
*/
|
|
1687
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, parse }) {
|
|
1688
|
+
const types = schema.type;
|
|
1689
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
1690
|
+
if (nonNullTypes.length <= 1) return null;
|
|
1691
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
1692
|
+
return ast.factory.createSchema({
|
|
1693
|
+
type: "union",
|
|
1694
|
+
members: nonNullTypes.map((t) => {
|
|
1695
|
+
return parse({
|
|
1696
|
+
schema: {
|
|
1697
|
+
...schema,
|
|
1698
|
+
type: t
|
|
1699
|
+
},
|
|
1700
|
+
name
|
|
1701
|
+
}, rawOptions);
|
|
1702
|
+
}),
|
|
1703
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1708
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1709
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1710
|
+
* match/convert/fall-through contract.
|
|
1711
|
+
*/
|
|
1712
|
+
const schemaRules = [
|
|
1713
|
+
{
|
|
1714
|
+
match: ({ schema }) => isReference(schema),
|
|
1715
|
+
convert: convertRef
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
1719
|
+
convert: convertAllOf
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
1723
|
+
convert: convertUnion
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1727
|
+
convert: convertConst
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
match: ({ schema }) => !!schema.format,
|
|
1731
|
+
convert: convertFormat
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
match: ({ schema }) => isBinary(schema),
|
|
1735
|
+
convert: convertBinary
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1739
|
+
convert: convertMultiType
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1743
|
+
convert: convertString
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1747
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
1751
|
+
convert: convertEnum
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1755
|
+
convert: convertObject
|
|
1756
|
+
},
|
|
1757
|
+
{
|
|
1758
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
1759
|
+
convert: convertTuple
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1763
|
+
convert: convertArray
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
match: ({ type }) => type === "string",
|
|
1767
|
+
convert: convertString
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
match: ({ type }) => type === "number",
|
|
1771
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
match: ({ type }) => type === "integer",
|
|
1775
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1776
|
+
},
|
|
1777
|
+
{
|
|
1778
|
+
match: ({ type }) => type === "boolean",
|
|
1779
|
+
convert: convertBoolean
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
match: ({ type }) => type === "null",
|
|
1783
|
+
convert: ({ schema, name, nullable }) => createNullNode(schema, name, nullable)
|
|
1731
1784
|
}
|
|
1785
|
+
];
|
|
1786
|
+
//#endregion
|
|
1787
|
+
//#region src/parser.ts
|
|
1788
|
+
/**
|
|
1789
|
+
* Creates the schema and operation converters bound to one OpenAPI document.
|
|
1790
|
+
*
|
|
1791
|
+
* Owns the per-instance `$ref` state (cycle detection, resolved-node cache, existence cache) and
|
|
1792
|
+
* the `parseSchema` recursion seam, then dispatches each schema through the ordered `schemaRules`
|
|
1793
|
+
* table from `converters.ts`. Every converter is a standalone function that recurses through the
|
|
1794
|
+
* `parse` function passed to it, so this file only wires state to the converters.
|
|
1795
|
+
*
|
|
1796
|
+
* @internal
|
|
1797
|
+
*/
|
|
1798
|
+
function createSchemaParser(ctx) {
|
|
1799
|
+
const document = ctx.document;
|
|
1732
1800
|
/**
|
|
1733
|
-
*
|
|
1801
|
+
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
1802
|
+
* recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
|
|
1734
1803
|
*/
|
|
1735
|
-
|
|
1736
|
-
return ast.factory.createSchema({
|
|
1737
|
-
type: "boolean",
|
|
1738
|
-
primitive: "boolean",
|
|
1739
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1804
|
+
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1742
1805
|
/**
|
|
1743
|
-
*
|
|
1744
|
-
*
|
|
1806
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1807
|
+
*
|
|
1808
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1809
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1810
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1811
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1745
1812
|
*/
|
|
1746
|
-
|
|
1747
|
-
return ast.factory.createSchema({
|
|
1748
|
-
type: "blob",
|
|
1749
|
-
primitive: "string",
|
|
1750
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1813
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1753
1814
|
/**
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
* falls through and handles it as that single type with nullability already folded in.
|
|
1815
|
+
* Memoized record of whether a `$ref` path resolves to a node the document actually defines.
|
|
1816
|
+
* A circular ref still resolves to an existing target, so this stays `true` for cycles and only
|
|
1817
|
+
* goes `false` for a `$ref` that points at a component the spec never declares.
|
|
1758
1818
|
*/
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
name
|
|
1772
|
-
}, rawOptions)),
|
|
1773
|
-
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1774
|
-
});
|
|
1819
|
+
const refExistence = /* @__PURE__ */ new Map();
|
|
1820
|
+
function refExists(refPath) {
|
|
1821
|
+
if (!refExistence.has(refPath)) {
|
|
1822
|
+
let exists = false;
|
|
1823
|
+
try {
|
|
1824
|
+
exists = !!resolveRef(document, refPath);
|
|
1825
|
+
} catch {
|
|
1826
|
+
exists = false;
|
|
1827
|
+
}
|
|
1828
|
+
refExistence.set(refPath, exists);
|
|
1829
|
+
}
|
|
1830
|
+
return refExistence.get(refPath) ?? false;
|
|
1775
1831
|
}
|
|
1776
1832
|
/**
|
|
1777
|
-
*
|
|
1778
|
-
* `
|
|
1779
|
-
*
|
|
1780
|
-
* match/convert/fall-through contract.
|
|
1833
|
+
* Resolves a `$ref` to its parsed node, guarding against cycles and memoizing per instance.
|
|
1834
|
+
* Returns `null` when the ref is currently being resolved (a cycle) or cannot be resolved
|
|
1835
|
+
* (e.g. a minimal document in a unit test).
|
|
1781
1836
|
*/
|
|
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)
|
|
1837
|
+
function resolveRefNode(refPath, rawOptions) {
|
|
1838
|
+
if (resolvingRefs.has(refPath)) return null;
|
|
1839
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1840
|
+
let resolved = null;
|
|
1841
|
+
try {
|
|
1842
|
+
const referenced = resolveRef(document, refPath);
|
|
1843
|
+
if (referenced) {
|
|
1844
|
+
resolvingRefs.add(refPath);
|
|
1845
|
+
resolved = parseSchema({ schema: referenced }, rawOptions);
|
|
1846
|
+
resolvingRefs.delete(refPath);
|
|
1847
|
+
}
|
|
1848
|
+
} catch {}
|
|
1849
|
+
resolvedRefCache.set(refPath, resolved);
|
|
1854
1850
|
}
|
|
1855
|
-
|
|
1851
|
+
return resolvedRefCache.get(refPath) ?? null;
|
|
1852
|
+
}
|
|
1856
1853
|
/**
|
|
1857
1854
|
* Converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1858
1855
|
*
|
|
@@ -1871,18 +1868,23 @@ function createSchemaParser(ctx) {
|
|
|
1871
1868
|
name
|
|
1872
1869
|
}, rawOptions);
|
|
1873
1870
|
const nullable = isNullable(schema) || void 0;
|
|
1874
|
-
const
|
|
1871
|
+
const context = {
|
|
1875
1872
|
schema,
|
|
1876
1873
|
name,
|
|
1877
1874
|
nullable,
|
|
1878
1875
|
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
1879
1876
|
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1880
1877
|
rawOptions,
|
|
1881
|
-
options
|
|
1878
|
+
options,
|
|
1879
|
+
parse: parseSchema,
|
|
1880
|
+
document,
|
|
1881
|
+
resolveRefNode,
|
|
1882
|
+
refExists,
|
|
1883
|
+
renames: ctx.renames
|
|
1882
1884
|
};
|
|
1883
1885
|
for (const rule of schemaRules) {
|
|
1884
|
-
if (!rule.match(
|
|
1885
|
-
const node = rule.convert(
|
|
1886
|
+
if (!rule.match(context)) continue;
|
|
1887
|
+
const node = rule.convert(context);
|
|
1886
1888
|
if (node) return node;
|
|
1887
1889
|
}
|
|
1888
1890
|
const emptyType = options.emptySchemaType;
|
|
@@ -2179,7 +2181,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
2179
2181
|
emptySchemaType,
|
|
2180
2182
|
enumSuffix
|
|
2181
2183
|
};
|
|
2182
|
-
let nameMapping = /* @__PURE__ */ new Map();
|
|
2183
2184
|
let parsedDocument = null;
|
|
2184
2185
|
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2185
2186
|
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2200,16 +2201,16 @@ const adapterOas = createAdapter((options) => {
|
|
|
2200
2201
|
const cached = schemasCache.get(document);
|
|
2201
2202
|
if (cached) return cached;
|
|
2202
2203
|
const result = getSchemas(document, { contentType });
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
return result.schemas;
|
|
2204
|
+
schemasCache.set(document, result);
|
|
2205
|
+
return result;
|
|
2206
2206
|
}
|
|
2207
|
-
function ensureSchemaParser(document) {
|
|
2207
|
+
function ensureSchemaParser({ document, renames }) {
|
|
2208
2208
|
const cached = schemaParserCache.get(document);
|
|
2209
2209
|
if (cached) return cached;
|
|
2210
2210
|
const parser = createSchemaParser({
|
|
2211
2211
|
document,
|
|
2212
|
-
contentType
|
|
2212
|
+
contentType,
|
|
2213
|
+
renames
|
|
2213
2214
|
});
|
|
2214
2215
|
schemaParserCache.set(document, parser);
|
|
2215
2216
|
return parser;
|
|
@@ -2291,8 +2292,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
2291
2292
|
integerType,
|
|
2292
2293
|
unknownType,
|
|
2293
2294
|
emptySchemaType,
|
|
2294
|
-
enumSuffix
|
|
2295
|
-
nameMapping
|
|
2295
|
+
enumSuffix
|
|
2296
2296
|
};
|
|
2297
2297
|
},
|
|
2298
2298
|
get document() {
|
|
@@ -2302,24 +2302,16 @@ const adapterOas = createAdapter((options) => {
|
|
|
2302
2302
|
await assertInputExists(input);
|
|
2303
2303
|
await validateDocument(await parseDocument(input), options);
|
|
2304
2304
|
},
|
|
2305
|
-
getImports(node, resolve) {
|
|
2306
|
-
return collect(node, { schema(schemaNode) {
|
|
2307
|
-
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
2308
|
-
if (!schemaRef?.ref) return null;
|
|
2309
|
-
const result = resolve(nameMapping.get(schemaRef.ref) ?? extractRefName(schemaRef.ref));
|
|
2310
|
-
if (!result) return null;
|
|
2311
|
-
return ast.factory.createImport({
|
|
2312
|
-
name: [result.name],
|
|
2313
|
-
path: result.path
|
|
2314
|
-
});
|
|
2315
|
-
} });
|
|
2316
|
-
},
|
|
2317
2305
|
async parse(source) {
|
|
2318
2306
|
const document = await ensureDocument(source);
|
|
2307
|
+
const { schemas, renames } = ensureSchemas(document);
|
|
2319
2308
|
return parseInput({
|
|
2320
2309
|
document,
|
|
2321
|
-
schemas
|
|
2322
|
-
parser: ensureSchemaParser(
|
|
2310
|
+
schemas,
|
|
2311
|
+
parser: ensureSchemaParser({
|
|
2312
|
+
document,
|
|
2313
|
+
renames
|
|
2314
|
+
})
|
|
2323
2315
|
});
|
|
2324
2316
|
}
|
|
2325
2317
|
};
|