@kubb/adapter-oas 5.0.0-beta.53 → 5.0.0-beta.55
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 +393 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +767 -10
- package/dist/index.js +393 -255
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/adapter.ts +13 -21
- package/src/bundler.ts +2 -2
- package/src/constants.ts +6 -0
- package/src/factory.ts +21 -25
- package/src/mime.ts +22 -0
- package/src/operation.ts +194 -0
- package/src/parser.ts +42 -30
- package/src/resolvers.ts +8 -9
- package/src/stream.ts +27 -23
- package/src/types.ts +46 -19
package/dist/index.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import "./chunk-C0LytTxp.js";
|
|
2
2
|
import { Diagnostics, ast, createAdapter } from "@kubb/core";
|
|
3
|
-
import BaseOas from "oas";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { access, readFile } from "node:fs/promises";
|
|
6
|
-
import
|
|
5
|
+
import { compileErrors, validate } from "@readme/openapi-parser";
|
|
6
|
+
import { parse } from "yaml";
|
|
7
7
|
import { bundle } from "api-ref-bundler";
|
|
8
|
-
import yaml from "js-yaml";
|
|
9
8
|
import { childName, enumPropName, extractRefName, findDiscriminator } from "@kubb/ast/utils";
|
|
10
|
-
import {
|
|
11
|
-
import { matchesMimeType } from "oas/utils";
|
|
9
|
+
import { collect, narrowSchema } from "@kubb/ast";
|
|
12
10
|
//#region src/constants.ts
|
|
13
11
|
/**
|
|
14
12
|
* Default parser options applied when no explicit options are provided.
|
|
@@ -40,6 +38,20 @@ const DEFAULT_PARSER_OPTIONS = {
|
|
|
40
38
|
*/
|
|
41
39
|
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
42
40
|
/**
|
|
41
|
+
* HTTP methods that count as operations on an OpenAPI path item. Other keys
|
|
42
|
+
* (`parameters`, `summary`, `$ref`, vendor extensions) are skipped when iterating operations.
|
|
43
|
+
*/
|
|
44
|
+
const SUPPORTED_METHODS = new Set([
|
|
45
|
+
"get",
|
|
46
|
+
"put",
|
|
47
|
+
"post",
|
|
48
|
+
"delete",
|
|
49
|
+
"options",
|
|
50
|
+
"head",
|
|
51
|
+
"patch",
|
|
52
|
+
"trace"
|
|
53
|
+
]);
|
|
54
|
+
/**
|
|
43
55
|
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
44
56
|
*/
|
|
45
57
|
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
@@ -152,77 +164,103 @@ const typeOptionMap = new Map([
|
|
|
152
164
|
function toCamelOrPascal(text, pascal) {
|
|
153
165
|
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
154
166
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
155
|
-
|
|
156
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
167
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
157
168
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
158
169
|
}
|
|
159
170
|
/**
|
|
160
|
-
*
|
|
161
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
162
|
-
* Segments are joined with `/` to form a file path.
|
|
171
|
+
* Converts `text` to camelCase.
|
|
163
172
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
173
|
+
* @example Word boundaries
|
|
174
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
166
175
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* and `'..'` transforms to an empty string). Without this filter the join would produce
|
|
170
|
-
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
|
|
171
|
-
* generated files to escape the configured output directory.
|
|
176
|
+
* @example With a prefix
|
|
177
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
172
178
|
*/
|
|
173
|
-
function
|
|
174
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
175
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Converts `text` to camelCase.
|
|
179
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
180
|
-
*
|
|
181
|
-
* @example
|
|
182
|
-
* camelCase('hello-world') // 'helloWorld'
|
|
183
|
-
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
184
|
-
*/
|
|
185
|
-
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
186
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
187
|
-
prefix,
|
|
188
|
-
suffix
|
|
189
|
-
} : {}));
|
|
179
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
190
180
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
191
181
|
}
|
|
192
182
|
/**
|
|
193
183
|
* Converts `text` to PascalCase.
|
|
194
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
195
184
|
*
|
|
196
|
-
* @example
|
|
197
|
-
* pascalCase('hello-world')
|
|
198
|
-
*
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
suffix
|
|
204
|
-
}) : camelCase(part));
|
|
185
|
+
* @example Word boundaries
|
|
186
|
+
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
187
|
+
*
|
|
188
|
+
* @example With a suffix
|
|
189
|
+
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
190
|
+
*/
|
|
191
|
+
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
205
192
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
206
193
|
}
|
|
207
194
|
//#endregion
|
|
208
195
|
//#region ../../internals/utils/src/runtime.ts
|
|
209
196
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
213
|
-
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
214
|
-
* otherwise look like Node.
|
|
197
|
+
* Detects the JavaScript runtime executing the current process and exposes its name and version.
|
|
215
198
|
*
|
|
216
|
-
* @
|
|
217
|
-
* ```ts
|
|
218
|
-
* if (isBun()) {
|
|
219
|
-
* await Bun.write(path, data)
|
|
220
|
-
* }
|
|
221
|
-
* ```
|
|
199
|
+
* Prefer the shared {@link runtime} instance over constructing your own.
|
|
222
200
|
*/
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
201
|
+
var Runtime = class {
|
|
202
|
+
/**
|
|
203
|
+
* `true` when the current process is running under Bun.
|
|
204
|
+
*
|
|
205
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
206
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
207
|
+
* otherwise look like Node.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* if (runtime.isBun) {
|
|
212
|
+
* await Bun.write(path, data)
|
|
213
|
+
* }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
get isBun() {
|
|
217
|
+
return typeof Bun !== "undefined";
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* `true` when the current process is running under Deno.
|
|
221
|
+
*/
|
|
222
|
+
get isDeno() {
|
|
223
|
+
return typeof globalThis.Deno !== "undefined";
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* `true` when the current process is running under Node.
|
|
227
|
+
*
|
|
228
|
+
* Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
|
|
229
|
+
*/
|
|
230
|
+
get isNode() {
|
|
231
|
+
return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Name of the runtime executing the current process.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
get name() {
|
|
242
|
+
if (this.isBun) return "bun";
|
|
243
|
+
if (this.isDeno) return "deno";
|
|
244
|
+
return "node";
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Version of the active runtime, or an empty string when it cannot be read.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* runtime.version // '1.3.11' under Bun, '22.22.2' under Node
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
get version() {
|
|
255
|
+
if (this.isBun) return process.versions.bun ?? "";
|
|
256
|
+
if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
|
|
257
|
+
return process.versions?.node ?? "";
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
|
|
262
|
+
*/
|
|
263
|
+
const runtime = new Runtime();
|
|
226
264
|
//#endregion
|
|
227
265
|
//#region ../../internals/utils/src/fs.ts
|
|
228
266
|
/**
|
|
@@ -237,7 +275,7 @@ function isBun() {
|
|
|
237
275
|
* ```
|
|
238
276
|
*/
|
|
239
277
|
async function exists(path) {
|
|
240
|
-
if (isBun
|
|
278
|
+
if (runtime.isBun) return Bun.file(path).exists();
|
|
241
279
|
return access(path).then(() => true, () => false);
|
|
242
280
|
}
|
|
243
281
|
/**
|
|
@@ -250,7 +288,7 @@ async function exists(path) {
|
|
|
250
288
|
* ```
|
|
251
289
|
*/
|
|
252
290
|
async function read(path) {
|
|
253
|
-
if (isBun
|
|
291
|
+
if (runtime.isBun) return Bun.file(path).text();
|
|
254
292
|
return readFile(path, { encoding: "utf8" });
|
|
255
293
|
}
|
|
256
294
|
//#endregion
|
|
@@ -407,99 +445,80 @@ function isIdentifier(name) {
|
|
|
407
445
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
408
446
|
}
|
|
409
447
|
//#endregion
|
|
410
|
-
//#region ../../internals/utils/src/
|
|
448
|
+
//#region ../../internals/utils/src/url.ts
|
|
449
|
+
function transformParam(raw, casing) {
|
|
450
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
451
|
+
return casing === "camelcase" ? camelCase(param) : param;
|
|
452
|
+
}
|
|
453
|
+
function toParamsObject(path, { replacer, casing } = {}) {
|
|
454
|
+
const params = {};
|
|
455
|
+
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
456
|
+
const param = transformParam(match[1], casing);
|
|
457
|
+
const key = replacer ? replacer(param) : param;
|
|
458
|
+
params[key] = key;
|
|
459
|
+
}
|
|
460
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
461
|
+
}
|
|
411
462
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* @example
|
|
415
|
-
* const p = new URLPath('/pet/{petId}')
|
|
416
|
-
* p.URL // '/pet/:petId'
|
|
417
|
-
* p.template // '`/pet/${petId}`'
|
|
463
|
+
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
418
464
|
*/
|
|
419
|
-
var
|
|
465
|
+
var Url = class Url {
|
|
420
466
|
/**
|
|
421
|
-
*
|
|
422
|
-
*/
|
|
423
|
-
path;
|
|
424
|
-
#options;
|
|
425
|
-
constructor(path, options = {}) {
|
|
426
|
-
this.path = path;
|
|
427
|
-
this.#options = options;
|
|
428
|
-
}
|
|
429
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
467
|
+
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
430
468
|
*
|
|
431
469
|
* @example
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* ```
|
|
470
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
471
|
+
* Url.canParse('/pet/{petId}') // false
|
|
435
472
|
*/
|
|
436
|
-
|
|
437
|
-
return
|
|
473
|
+
static canParse(url, base) {
|
|
474
|
+
return URL.canParse(url, base);
|
|
438
475
|
}
|
|
439
|
-
/**
|
|
476
|
+
/**
|
|
477
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
440
478
|
*
|
|
441
479
|
* @example
|
|
442
|
-
*
|
|
443
|
-
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
444
|
-
* new URLPath('/pet/{petId}').isURL // false
|
|
445
|
-
* ```
|
|
480
|
+
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
446
481
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
return !!new URL(this.path).href;
|
|
450
|
-
} catch {
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
482
|
+
static toPath(path) {
|
|
483
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
453
484
|
}
|
|
454
485
|
/**
|
|
455
|
-
* Converts
|
|
486
|
+
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
487
|
+
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
488
|
+
* and `casing` controls parameter identifier casing.
|
|
456
489
|
*
|
|
457
490
|
* @example
|
|
458
|
-
*
|
|
459
|
-
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
460
|
-
*/
|
|
461
|
-
get template() {
|
|
462
|
-
return this.toTemplateString();
|
|
463
|
-
}
|
|
464
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
491
|
+
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
465
492
|
*
|
|
466
493
|
* @example
|
|
467
|
-
*
|
|
468
|
-
* new URLPath('/pet/{petId}').object
|
|
469
|
-
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
470
|
-
* ```
|
|
494
|
+
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
471
495
|
*/
|
|
472
|
-
|
|
473
|
-
|
|
496
|
+
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
497
|
+
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
498
|
+
if (i % 2 === 0) return part;
|
|
499
|
+
const param = transformParam(part, casing);
|
|
500
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
501
|
+
}).join("");
|
|
502
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
474
503
|
}
|
|
475
|
-
/**
|
|
504
|
+
/**
|
|
505
|
+
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
506
|
+
* expression when `stringify` is set.
|
|
476
507
|
*
|
|
477
508
|
* @example
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* new URLPath('/pet').params // undefined
|
|
481
|
-
* ```
|
|
482
|
-
*/
|
|
483
|
-
get params() {
|
|
484
|
-
return this.getParams();
|
|
485
|
-
}
|
|
486
|
-
#transformParam(raw) {
|
|
487
|
-
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
488
|
-
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
509
|
+
* Url.toObject('/pet/{petId}')
|
|
510
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
492
511
|
*/
|
|
493
|
-
|
|
494
|
-
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
495
|
-
const raw = match[1];
|
|
496
|
-
fn(raw, this.#transformParam(raw));
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
toObject({ type = "path", replacer, stringify } = {}) {
|
|
512
|
+
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
500
513
|
const object = {
|
|
501
|
-
url: type === "path" ?
|
|
502
|
-
|
|
514
|
+
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
515
|
+
replacer,
|
|
516
|
+
casing
|
|
517
|
+
}),
|
|
518
|
+
params: toParamsObject(path, {
|
|
519
|
+
replacer,
|
|
520
|
+
casing
|
|
521
|
+
})
|
|
503
522
|
};
|
|
504
523
|
if (stringify) {
|
|
505
524
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -508,50 +527,6 @@ var URLPath = class {
|
|
|
508
527
|
}
|
|
509
528
|
return object;
|
|
510
529
|
}
|
|
511
|
-
/**
|
|
512
|
-
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
513
|
-
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
514
|
-
*
|
|
515
|
-
* @example
|
|
516
|
-
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
517
|
-
*/
|
|
518
|
-
toTemplateString({ prefix, replacer } = {}) {
|
|
519
|
-
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
520
|
-
if (i % 2 === 0) return part;
|
|
521
|
-
const param = this.#transformParam(part);
|
|
522
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
523
|
-
}).join("");
|
|
524
|
-
return `\`${prefix ?? ""}${result}\``;
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
528
|
-
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
529
|
-
* Returns `undefined` when no path parameters are found.
|
|
530
|
-
*
|
|
531
|
-
* @example
|
|
532
|
-
* ```ts
|
|
533
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
534
|
-
* // { petId: 'petId', tagId: 'tagId' }
|
|
535
|
-
* ```
|
|
536
|
-
*/
|
|
537
|
-
getParams(replacer) {
|
|
538
|
-
const params = {};
|
|
539
|
-
this.#eachParam((_raw, param) => {
|
|
540
|
-
const key = replacer ? replacer(param) : param;
|
|
541
|
-
params[key] = key;
|
|
542
|
-
});
|
|
543
|
-
return Object.keys(params).length > 0 ? params : void 0;
|
|
544
|
-
}
|
|
545
|
-
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
546
|
-
*
|
|
547
|
-
* @example
|
|
548
|
-
* ```ts
|
|
549
|
-
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
550
|
-
* ```
|
|
551
|
-
*/
|
|
552
|
-
toURLPath() {
|
|
553
|
-
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
554
|
-
}
|
|
555
530
|
};
|
|
556
531
|
//#endregion
|
|
557
532
|
//#region src/bundler.ts
|
|
@@ -568,7 +543,7 @@ async function readSource(sourcePath) {
|
|
|
568
543
|
async function resolveSource(sourcePath) {
|
|
569
544
|
const data = await readSource(sourcePath);
|
|
570
545
|
if (sourcePath.toLowerCase().endsWith(".md")) return data;
|
|
571
|
-
return
|
|
546
|
+
return parse(data);
|
|
572
547
|
}
|
|
573
548
|
/**
|
|
574
549
|
* Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
|
|
@@ -673,15 +648,9 @@ function isDiscriminator(obj) {
|
|
|
673
648
|
* const document = await parse(rawDocumentObject, { canBundle: false })
|
|
674
649
|
* ```
|
|
675
650
|
*/
|
|
676
|
-
async function parseDocument(pathOrApi, { canBundle = true
|
|
677
|
-
if (typeof pathOrApi === "string" && canBundle) return parseDocument(await bundleDocument(pathOrApi), {
|
|
678
|
-
|
|
679
|
-
enablePaths
|
|
680
|
-
});
|
|
681
|
-
const document = await new OASNormalize(pathOrApi, {
|
|
682
|
-
enablePaths,
|
|
683
|
-
colorizeErrors: true
|
|
684
|
-
}).load();
|
|
651
|
+
async function parseDocument(pathOrApi, { canBundle = true } = {}) {
|
|
652
|
+
if (typeof pathOrApi === "string" && canBundle) return parseDocument(await bundleDocument(pathOrApi), { canBundle: false });
|
|
653
|
+
const document = typeof pathOrApi === "string" ? parse(pathOrApi) : pathOrApi;
|
|
685
654
|
if (isOpenApiV2Document(document)) {
|
|
686
655
|
const { default: swagger2openapi } = await import("swagger2openapi");
|
|
687
656
|
const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
|
|
@@ -692,7 +661,7 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
692
661
|
/**
|
|
693
662
|
* Deep-merges multiple OpenAPI documents into a single `Document`.
|
|
694
663
|
*
|
|
695
|
-
* Each document is parsed independently then
|
|
664
|
+
* Each document is parsed independently, then deep-merged into one in array order.
|
|
696
665
|
* Throws when the input array is empty.
|
|
697
666
|
*
|
|
698
667
|
* @example
|
|
@@ -701,10 +670,7 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
701
670
|
* ```
|
|
702
671
|
*/
|
|
703
672
|
async function mergeDocuments(pathOrApi) {
|
|
704
|
-
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
705
|
-
enablePaths: false,
|
|
706
|
-
canBundle: false
|
|
707
|
-
})));
|
|
673
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, { canBundle: false })));
|
|
708
674
|
if (documents.length === 0) throw new Diagnostics.Error({
|
|
709
675
|
code: Diagnostics.code.inputRequired,
|
|
710
676
|
severity: "error",
|
|
@@ -743,7 +709,7 @@ async function parseFromConfig(source) {
|
|
|
743
709
|
return parseDocument(source.data, { canBundle: false });
|
|
744
710
|
}
|
|
745
711
|
if (source.type === "paths") return mergeDocuments(source.paths);
|
|
746
|
-
if (
|
|
712
|
+
if (Url.canParse(source.path)) return parseDocument(source.path);
|
|
747
713
|
const resolved = path.resolve(path.dirname(source.path), source.path);
|
|
748
714
|
await assertInputExists(resolved);
|
|
749
715
|
return parseDocument(resolved);
|
|
@@ -754,7 +720,7 @@ async function parseFromConfig(source) {
|
|
|
754
720
|
* its parse error instead.
|
|
755
721
|
*/
|
|
756
722
|
async function assertInputExists(input) {
|
|
757
|
-
if (
|
|
723
|
+
if (Url.canParse(input)) return;
|
|
758
724
|
if (!await exists(input)) throw new Diagnostics.Error({
|
|
759
725
|
code: Diagnostics.code.inputNotFound,
|
|
760
726
|
severity: "error",
|
|
@@ -764,7 +730,7 @@ async function assertInputExists(input) {
|
|
|
764
730
|
});
|
|
765
731
|
}
|
|
766
732
|
/**
|
|
767
|
-
* Validates an OpenAPI document using
|
|
733
|
+
* Validates an OpenAPI document using `@readme/openapi-parser` with colorized error output.
|
|
768
734
|
*
|
|
769
735
|
* @example
|
|
770
736
|
* ```ts
|
|
@@ -773,10 +739,8 @@ async function assertInputExists(input) {
|
|
|
773
739
|
*/
|
|
774
740
|
async function validateDocument(document, { throwOnError = false } = {}) {
|
|
775
741
|
try {
|
|
776
|
-
await
|
|
777
|
-
|
|
778
|
-
colorizeErrors: true
|
|
779
|
-
}).validate({ parser: { validate: { errors: { colorize: true } } } });
|
|
742
|
+
const result = await validate(structuredClone(document), { validate: { errors: { colorize: true } } });
|
|
743
|
+
if (!result.valid) throw new Error(compileErrors(result));
|
|
780
744
|
} catch (error) {
|
|
781
745
|
if (throwOnError) throw error;
|
|
782
746
|
}
|
|
@@ -876,6 +840,161 @@ const oasDialect = ast.defineSchemaDialect({
|
|
|
876
840
|
resolveRef
|
|
877
841
|
});
|
|
878
842
|
//#endregion
|
|
843
|
+
//#region src/mime.ts
|
|
844
|
+
/**
|
|
845
|
+
* MIME type fragments that mark a media type as JSON-like.
|
|
846
|
+
*
|
|
847
|
+
* Matches the substrings used by the `oas` library's `matchesMimeType.json`, so a content type is
|
|
848
|
+
* JSON when it contains any of these. The `+json` entry catches structured-syntax suffixes such as
|
|
849
|
+
* `application/vnd.api+json`.
|
|
850
|
+
*/
|
|
851
|
+
const jsonMimeFragments = [
|
|
852
|
+
"application/json",
|
|
853
|
+
"application/x-json",
|
|
854
|
+
"text/json",
|
|
855
|
+
"text/x-json",
|
|
856
|
+
"+json"
|
|
857
|
+
];
|
|
858
|
+
/**
|
|
859
|
+
* Returns `true` when a media type string is JSON-like.
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* ```ts
|
|
863
|
+
* isJsonMimeType('application/json') // true
|
|
864
|
+
* isJsonMimeType('application/vnd.api+json') // true
|
|
865
|
+
* isJsonMimeType('multipart/form-data') // false
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
868
|
+
function isJsonMimeType(mimeType) {
|
|
869
|
+
return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/operation.ts
|
|
873
|
+
/**
|
|
874
|
+
* Slugifies a path for the `operationId` fallback: non-alphanumerics collapse to single dashes,
|
|
875
|
+
* with no leading or trailing dash.
|
|
876
|
+
*/
|
|
877
|
+
function slugify(value) {
|
|
878
|
+
return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
|
|
882
|
+
*/
|
|
883
|
+
function getOperationId({ path, method, schema }) {
|
|
884
|
+
const { operationId } = schema;
|
|
885
|
+
if (typeof operationId === "string" && operationId.length > 0) return operationId;
|
|
886
|
+
return `${method}_${slugify(path).toLowerCase()}`;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Returns the declared response status codes, skipping `x-` extensions and non-object entries.
|
|
890
|
+
*/
|
|
891
|
+
function getResponseStatusCodes({ schema }) {
|
|
892
|
+
const responses = schema.responses;
|
|
893
|
+
if (!responses || isReference(responses)) return [];
|
|
894
|
+
return Object.keys(responses).filter((key) => !key.startsWith("x-") && !!responses[key] && typeof responses[key] === "object");
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Returns the response object for a status code, resolving a `$ref` in place. `false` when absent.
|
|
898
|
+
*/
|
|
899
|
+
function getResponseByStatusCode({ document, operation, statusCode }) {
|
|
900
|
+
const responses = operation.schema.responses;
|
|
901
|
+
if (!responses || isReference(responses)) return false;
|
|
902
|
+
const response = responses[statusCode];
|
|
903
|
+
if (!response) return false;
|
|
904
|
+
if (isReference(response)) {
|
|
905
|
+
const resolved = resolveRef(document, response.$ref);
|
|
906
|
+
responses[statusCode] = resolved;
|
|
907
|
+
if (!resolved || isReference(resolved)) return false;
|
|
908
|
+
return resolved;
|
|
909
|
+
}
|
|
910
|
+
return response;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
|
|
914
|
+
* `undefined` when the operation has no request body.
|
|
915
|
+
*/
|
|
916
|
+
function getRequestBodyContent({ document, operation }) {
|
|
917
|
+
const { schema } = operation;
|
|
918
|
+
let requestBody = schema.requestBody;
|
|
919
|
+
if (!requestBody) return;
|
|
920
|
+
if (isReference(requestBody)) {
|
|
921
|
+
const resolved = resolveRef(document, requestBody.$ref);
|
|
922
|
+
schema.requestBody = resolved;
|
|
923
|
+
if (!resolved || isReference(resolved)) return;
|
|
924
|
+
requestBody = resolved;
|
|
925
|
+
}
|
|
926
|
+
return requestBody.content;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Returns the request body media type. With `mediaType` set, returns that entry or `false`.
|
|
930
|
+
* Otherwise picks the first JSON-like media type, then the first declared one, as a
|
|
931
|
+
* `[mediaType, object]` tuple.
|
|
932
|
+
*/
|
|
933
|
+
function getRequestContent({ document, operation, mediaType }) {
|
|
934
|
+
const content = getRequestBodyContent({
|
|
935
|
+
document,
|
|
936
|
+
operation
|
|
937
|
+
});
|
|
938
|
+
if (!content) return false;
|
|
939
|
+
if (mediaType) return mediaType in content ? content[mediaType] : false;
|
|
940
|
+
const mediaTypes = Object.keys(content);
|
|
941
|
+
const available = mediaTypes.find((mt) => isJsonMimeType(mt)) ?? mediaTypes[0];
|
|
942
|
+
return available ? [available, content[available]] : false;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins,
|
|
946
|
+
* matching the previous behavior), then the first declared one, defaulting to `'application/json'`.
|
|
947
|
+
*/
|
|
948
|
+
function getRequestContentType({ document, operation }) {
|
|
949
|
+
const content = getRequestBodyContent({
|
|
950
|
+
document,
|
|
951
|
+
operation
|
|
952
|
+
});
|
|
953
|
+
const mediaTypes = content ? Object.keys(content) : [];
|
|
954
|
+
let result = mediaTypes[0] ?? "application/json";
|
|
955
|
+
for (const mt of mediaTypes) if (isJsonMimeType(mt)) result = mt;
|
|
956
|
+
return result;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Builds an `Operation` for every supported HTTP method on every path, in document order.
|
|
960
|
+
* `x-` path keys and unresolvable path-item `$ref`s are skipped.
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```ts
|
|
964
|
+
* for (const operation of getOperations(document)) {
|
|
965
|
+
* parseOperation(options, operation)
|
|
966
|
+
* }
|
|
967
|
+
* ```
|
|
968
|
+
*/
|
|
969
|
+
function getOperations(document) {
|
|
970
|
+
const operations = [];
|
|
971
|
+
const paths = document.paths;
|
|
972
|
+
if (!paths) return operations;
|
|
973
|
+
for (const path of Object.keys(paths)) {
|
|
974
|
+
if (path.startsWith("x-")) continue;
|
|
975
|
+
let pathItem = paths[path];
|
|
976
|
+
if (!pathItem) continue;
|
|
977
|
+
if (isReference(pathItem)) {
|
|
978
|
+
const resolved = resolveRef(document, pathItem.$ref);
|
|
979
|
+
paths[path] = resolved;
|
|
980
|
+
if (!resolved || isReference(resolved)) continue;
|
|
981
|
+
pathItem = resolved;
|
|
982
|
+
}
|
|
983
|
+
const item = pathItem;
|
|
984
|
+
for (const method of Object.keys(item)) {
|
|
985
|
+
if (!SUPPORTED_METHODS.has(method)) continue;
|
|
986
|
+
const schema = item[method];
|
|
987
|
+
if (!schema || typeof schema !== "object") continue;
|
|
988
|
+
operations.push({
|
|
989
|
+
path,
|
|
990
|
+
method,
|
|
991
|
+
schema
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return operations;
|
|
996
|
+
}
|
|
997
|
+
//#endregion
|
|
879
998
|
//#region src/resolvers.ts
|
|
880
999
|
/**
|
|
881
1000
|
* Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
|
|
@@ -968,7 +1087,7 @@ function getResponseBody(responseBody, contentType) {
|
|
|
968
1087
|
}
|
|
969
1088
|
let availableContentType;
|
|
970
1089
|
const contentTypes = Object.keys(body.content);
|
|
971
|
-
for (const mt of contentTypes) if (
|
|
1090
|
+
for (const mt of contentTypes) if (isJsonMimeType(mt)) {
|
|
972
1091
|
availableContentType = mt;
|
|
973
1092
|
break;
|
|
974
1093
|
}
|
|
@@ -999,7 +1118,11 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
|
999
1118
|
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
1000
1119
|
}
|
|
1001
1120
|
}
|
|
1002
|
-
const responseBody = getResponseBody(
|
|
1121
|
+
const responseBody = getResponseBody(getResponseByStatusCode({
|
|
1122
|
+
document,
|
|
1123
|
+
operation,
|
|
1124
|
+
statusCode
|
|
1125
|
+
}), options.contentType);
|
|
1003
1126
|
if (responseBody === false) return {};
|
|
1004
1127
|
const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
|
|
1005
1128
|
if (!schema) return {};
|
|
@@ -1015,7 +1138,11 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
|
1015
1138
|
*/
|
|
1016
1139
|
function getRequestSchema(document, operation, options = {}) {
|
|
1017
1140
|
if (operation.schema.requestBody) operation.schema.requestBody = dereferenceWithRef(document, operation.schema.requestBody);
|
|
1018
|
-
const requestBody =
|
|
1141
|
+
const requestBody = getRequestContent({
|
|
1142
|
+
document,
|
|
1143
|
+
operation,
|
|
1144
|
+
mediaType: options.contentType
|
|
1145
|
+
});
|
|
1019
1146
|
if (requestBody === false) return null;
|
|
1020
1147
|
const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
|
|
1021
1148
|
if (!schema) return null;
|
|
@@ -1050,7 +1177,7 @@ function hasStructuralKeywords(fragment) {
|
|
|
1050
1177
|
function flattenSchema(schema) {
|
|
1051
1178
|
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
|
|
1052
1179
|
const allOfFragments = schema.allOf;
|
|
1053
|
-
if (allOfFragments.some((item) =>
|
|
1180
|
+
if (allOfFragments.some((item) => isReference(item))) return schema;
|
|
1054
1181
|
if (allOfFragments.some(hasStructuralKeywords)) return schema;
|
|
1055
1182
|
const merged = { ...schema };
|
|
1056
1183
|
delete merged.allOf;
|
|
@@ -1288,7 +1415,11 @@ function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
|
1288
1415
|
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
1289
1416
|
}
|
|
1290
1417
|
}
|
|
1291
|
-
const responseObj =
|
|
1418
|
+
const responseObj = getResponseByStatusCode({
|
|
1419
|
+
document,
|
|
1420
|
+
operation,
|
|
1421
|
+
statusCode
|
|
1422
|
+
});
|
|
1292
1423
|
if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
|
|
1293
1424
|
const body = responseObj;
|
|
1294
1425
|
return body.content ? Object.keys(body.content) : [];
|
|
@@ -1872,7 +2003,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1872
2003
|
});
|
|
1873
2004
|
}
|
|
1874
2005
|
/**
|
|
1875
|
-
* Ordered schema
|
|
2006
|
+
* Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1876
2007
|
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1877
2008
|
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1878
2009
|
* match/convert/fall-through contract.
|
|
@@ -1970,10 +2101,10 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1970
2101
|
}
|
|
1971
2102
|
];
|
|
1972
2103
|
/**
|
|
1973
|
-
*
|
|
2104
|
+
* Converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1974
2105
|
*
|
|
1975
|
-
* Builds the per-schema context, then
|
|
1976
|
-
*
|
|
2106
|
+
* Builds the per-schema context, then walks the ordered {@link schemaRules} table and returns
|
|
2107
|
+
* the first converter that produces a node. When none match, falls back to the configured
|
|
1977
2108
|
* `emptySchemaType`.
|
|
1978
2109
|
*/
|
|
1979
2110
|
function parseSchema({ schema, name }, rawOptions) {
|
|
@@ -1996,8 +2127,11 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1996
2127
|
rawOptions,
|
|
1997
2128
|
options
|
|
1998
2129
|
};
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2130
|
+
for (const rule of schemaRules) {
|
|
2131
|
+
if (!rule.match(schemaCtx)) continue;
|
|
2132
|
+
const node = rule.convert(schemaCtx);
|
|
2133
|
+
if (node) return node;
|
|
2134
|
+
}
|
|
2001
2135
|
const emptyType = typeOptionMap.get(options.emptySchemaType);
|
|
2002
2136
|
return ast.createSchema({
|
|
2003
2137
|
type: emptyType,
|
|
@@ -2068,7 +2202,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2068
2202
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
2069
2203
|
*/
|
|
2070
2204
|
function parseOperation(options, operation) {
|
|
2071
|
-
const operationId =
|
|
2205
|
+
const operationId = getOperationId(operation);
|
|
2072
2206
|
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
2073
2207
|
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
|
|
2074
2208
|
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
|
|
@@ -2091,8 +2225,12 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2091
2225
|
required: requestBodyMeta.required || void 0,
|
|
2092
2226
|
content: content.length > 0 ? content : void 0
|
|
2093
2227
|
} : void 0;
|
|
2094
|
-
const responses =
|
|
2095
|
-
const responseObj =
|
|
2228
|
+
const responses = getResponseStatusCodes(operation).map((statusCode) => {
|
|
2229
|
+
const responseObj = getResponseByStatusCode({
|
|
2230
|
+
document,
|
|
2231
|
+
operation,
|
|
2232
|
+
statusCode
|
|
2233
|
+
});
|
|
2096
2234
|
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
2097
2235
|
const { description } = getResponseMeta(responseObj);
|
|
2098
2236
|
const parseEntrySchema = (contentType) => {
|
|
@@ -2110,7 +2248,10 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2110
2248
|
...parseEntrySchema(contentType)
|
|
2111
2249
|
}));
|
|
2112
2250
|
if (content.length === 0) content.push({
|
|
2113
|
-
contentType:
|
|
2251
|
+
contentType: getRequestContentType({
|
|
2252
|
+
document,
|
|
2253
|
+
operation
|
|
2254
|
+
}) || "application/json",
|
|
2114
2255
|
...parseEntrySchema(ctx.contentType)
|
|
2115
2256
|
});
|
|
2116
2257
|
return ast.createResponse({
|
|
@@ -2119,16 +2260,23 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2119
2260
|
content
|
|
2120
2261
|
});
|
|
2121
2262
|
});
|
|
2122
|
-
const
|
|
2263
|
+
const pathItem = document.paths?.[operation.path];
|
|
2264
|
+
const pathItemDoc = pathItem && !dialect.isReference(pathItem) ? pathItem : void 0;
|
|
2265
|
+
const pickDoc = (key) => {
|
|
2266
|
+
const own = operation.schema[key];
|
|
2267
|
+
if (typeof own === "string") return own;
|
|
2268
|
+
const fallback = pathItemDoc?.[key];
|
|
2269
|
+
return typeof fallback === "string" ? fallback : void 0;
|
|
2270
|
+
};
|
|
2123
2271
|
return ast.createOperation({
|
|
2124
2272
|
operationId,
|
|
2125
2273
|
protocol: "http",
|
|
2126
2274
|
method: operation.method.toUpperCase(),
|
|
2127
|
-
path:
|
|
2128
|
-
tags: operation.
|
|
2129
|
-
summary:
|
|
2130
|
-
description:
|
|
2131
|
-
deprecated: operation.
|
|
2275
|
+
path: operation.path,
|
|
2276
|
+
tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
|
|
2277
|
+
summary: pickDoc("summary") || void 0,
|
|
2278
|
+
description: pickDoc("description") || void 0,
|
|
2279
|
+
deprecated: operation.schema.deprecated || void 0,
|
|
2132
2280
|
parameters,
|
|
2133
2281
|
requestBody,
|
|
2134
2282
|
responses
|
|
@@ -2346,7 +2494,7 @@ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
|
2346
2494
|
* })
|
|
2347
2495
|
* ```
|
|
2348
2496
|
*/
|
|
2349
|
-
function preScan({ schemas, parseSchema, parseOperation,
|
|
2497
|
+
function preScan({ schemas, parseSchema, parseOperation, document, parserOptions, discriminator, dedupe }) {
|
|
2350
2498
|
const allNodes = [];
|
|
2351
2499
|
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2352
2500
|
const enumNames = [];
|
|
@@ -2370,8 +2518,7 @@ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions,
|
|
|
2370
2518
|
let dedupePlan = null;
|
|
2371
2519
|
if (dedupe) {
|
|
2372
2520
|
const operationNodes = [];
|
|
2373
|
-
for (const
|
|
2374
|
-
if (!operation) continue;
|
|
2521
|
+
for (const operation of getOperations(document)) {
|
|
2375
2522
|
const operationNode = parseOperation(parserOptions, operation);
|
|
2376
2523
|
if (operationNode) operationNodes.push(operationNode);
|
|
2377
2524
|
}
|
|
@@ -2383,16 +2530,17 @@ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions,
|
|
|
2383
2530
|
});
|
|
2384
2531
|
for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
|
|
2385
2532
|
}
|
|
2533
|
+
const aliasNames = dedupePlan?.aliasNames;
|
|
2386
2534
|
return {
|
|
2387
2535
|
refAliasMap,
|
|
2388
|
-
enumNames,
|
|
2536
|
+
enumNames: aliasNames && aliasNames.size > 0 ? enumNames.filter((name) => !aliasNames.has(name)) : enumNames,
|
|
2389
2537
|
circularNames,
|
|
2390
2538
|
discriminatorChildMap,
|
|
2391
2539
|
dedupePlan
|
|
2392
2540
|
};
|
|
2393
2541
|
}
|
|
2394
2542
|
/**
|
|
2395
|
-
* Creates a lazy `
|
|
2543
|
+
* Creates a lazy `InputNode<true>` from already-resolved adapter state.
|
|
2396
2544
|
*
|
|
2397
2545
|
* The schema and operation iterables each start a fresh parse pass on every
|
|
2398
2546
|
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
@@ -2403,16 +2551,16 @@ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions,
|
|
|
2403
2551
|
*
|
|
2404
2552
|
* @example
|
|
2405
2553
|
* ```ts
|
|
2406
|
-
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation,
|
|
2554
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2407
2555
|
* for await (const schema of streamNode.schemas) {
|
|
2408
2556
|
* // each call to for-await restarts from the first schema
|
|
2409
2557
|
* }
|
|
2410
2558
|
* ```
|
|
2411
2559
|
*/
|
|
2412
|
-
function createInputStream({ schemas, parseSchema, parseOperation,
|
|
2560
|
+
function createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
|
|
2413
2561
|
const rewriteTopLevelSchema = (node) => {
|
|
2414
2562
|
if (!dedupePlan) return node;
|
|
2415
|
-
const canonical = dedupePlan.canonicalBySignature.get(ast.
|
|
2563
|
+
const canonical = dedupePlan.canonicalBySignature.get(ast.signatureOf(node));
|
|
2416
2564
|
if (canonical && canonical.name !== node.name) return ast.createSchema({
|
|
2417
2565
|
type: "ref",
|
|
2418
2566
|
name: node.name ?? null,
|
|
@@ -2420,12 +2568,13 @@ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, pars
|
|
|
2420
2568
|
description: node.description,
|
|
2421
2569
|
deprecated: node.deprecated
|
|
2422
2570
|
});
|
|
2423
|
-
return ast.applyDedupe(node, dedupePlan
|
|
2571
|
+
return ast.applyDedupe(node, dedupePlan, true);
|
|
2424
2572
|
};
|
|
2425
2573
|
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2426
2574
|
return (async function* () {
|
|
2427
2575
|
if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
|
|
2428
2576
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
2577
|
+
if (dedupePlan?.aliasNames.has(name)) continue;
|
|
2429
2578
|
const alias = refAliasMap.get(name);
|
|
2430
2579
|
if (alias?.name && schemas[alias.name]) {
|
|
2431
2580
|
yield rewriteTopLevelSchema({
|
|
@@ -2447,10 +2596,9 @@ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, pars
|
|
|
2447
2596
|
} };
|
|
2448
2597
|
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2449
2598
|
return (async function* () {
|
|
2450
|
-
for (const
|
|
2451
|
-
if (!operation) continue;
|
|
2599
|
+
for (const operation of getOperations(document)) {
|
|
2452
2600
|
const node = parseOperation(parserOptions, operation);
|
|
2453
|
-
if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan
|
|
2601
|
+
if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan) : node;
|
|
2454
2602
|
}
|
|
2455
2603
|
})();
|
|
2456
2604
|
} };
|
|
@@ -2503,7 +2651,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
2503
2651
|
let parsedDocument = null;
|
|
2504
2652
|
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2505
2653
|
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2506
|
-
const baseOasCache = /* @__PURE__ */ new WeakMap();
|
|
2507
2654
|
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2508
2655
|
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2509
2656
|
function ensureDocument(source) {
|
|
@@ -2529,13 +2676,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
2529
2676
|
schemasCache.set(document, promise);
|
|
2530
2677
|
return promise;
|
|
2531
2678
|
}
|
|
2532
|
-
function ensureBaseOas(document) {
|
|
2533
|
-
const cached = baseOasCache.get(document);
|
|
2534
|
-
if (cached) return cached;
|
|
2535
|
-
const baseOas = new BaseOas(document);
|
|
2536
|
-
baseOasCache.set(document, baseOas);
|
|
2537
|
-
return baseOas;
|
|
2538
|
-
}
|
|
2539
2679
|
function ensureSchemaParser(document) {
|
|
2540
2680
|
const cached = schemaParserCache.get(document);
|
|
2541
2681
|
if (cached) return cached;
|
|
@@ -2546,14 +2686,14 @@ const adapterOas = createAdapter((options) => {
|
|
|
2546
2686
|
schemaParserCache.set(document, parser);
|
|
2547
2687
|
return parser;
|
|
2548
2688
|
}
|
|
2549
|
-
function ensurePreScan(document, schemas, parseSchema, parseOperation
|
|
2689
|
+
function ensurePreScan(document, schemas, parseSchema, parseOperation) {
|
|
2550
2690
|
const cached = preScanCache.get(document);
|
|
2551
2691
|
if (cached) return cached;
|
|
2552
2692
|
const result = preScan({
|
|
2553
2693
|
schemas,
|
|
2554
2694
|
parseSchema,
|
|
2555
2695
|
parseOperation,
|
|
2556
|
-
|
|
2696
|
+
document,
|
|
2557
2697
|
parserOptions,
|
|
2558
2698
|
discriminator,
|
|
2559
2699
|
dedupe
|
|
@@ -2565,13 +2705,12 @@ const adapterOas = createAdapter((options) => {
|
|
|
2565
2705
|
const document = await ensureDocument(source);
|
|
2566
2706
|
const schemas = await ensureSchemas(document);
|
|
2567
2707
|
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2568
|
-
const
|
|
2569
|
-
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
|
|
2708
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation);
|
|
2570
2709
|
return createInputStream({
|
|
2571
2710
|
schemas,
|
|
2572
2711
|
parseSchema,
|
|
2573
2712
|
parseOperation,
|
|
2574
|
-
|
|
2713
|
+
document,
|
|
2575
2714
|
parserOptions,
|
|
2576
2715
|
refAliasMap,
|
|
2577
2716
|
discriminatorChildMap,
|
|
@@ -2616,18 +2755,17 @@ const adapterOas = createAdapter((options) => {
|
|
|
2616
2755
|
await validateDocument(await parseDocument(input), options);
|
|
2617
2756
|
},
|
|
2618
2757
|
getImports(node, resolve) {
|
|
2619
|
-
return
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
});
|
|
2758
|
+
return collect(node, { schema(schemaNode) {
|
|
2759
|
+
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
2760
|
+
if (!schemaRef?.ref) return null;
|
|
2761
|
+
const rawName = extractRefName(schemaRef.ref);
|
|
2762
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
2763
|
+
if (!result) return null;
|
|
2764
|
+
return ast.createImport({
|
|
2765
|
+
name: [result.name],
|
|
2766
|
+
path: result.path
|
|
2767
|
+
});
|
|
2768
|
+
} });
|
|
2631
2769
|
},
|
|
2632
2770
|
async parse(source) {
|
|
2633
2771
|
const streamNode = await createStream(source);
|