@ebarahona/loopback-openapi-v3 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -72
- package/dist/errors.d.ts +19 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +1 -1
- package/dist/keys.js.map +1 -1
- package/dist/openapi-version.component.d.ts +2 -1
- package/dist/openapi-version.component.js.map +1 -1
- package/dist/openapi-version.enhancer.js.map +1 -1
- package/dist/transform.d.ts +1 -1
- package/dist/transform.js +58 -17
- package/dist/transform.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +18 -5
- package/src/__tests__/openapi-version.enhancer.spec.ts +1 -1
- package/src/__tests__/transform.spec.ts +122 -51
- package/src/errors.ts +83 -0
- package/src/index.ts +26 -6
- package/src/keys.ts +17 -1
- package/src/openapi-version.component.ts +4 -5
- package/src/openapi-version.enhancer.ts +8 -9
- package/src/transform.ts +209 -41
- package/src/types.ts +16 -0
package/src/keys.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import {BindingKey} from '@loopback/core';
|
|
2
|
-
import {OpenApiVersionConfig} from './types';
|
|
2
|
+
import type {OpenApiVersionConfig} from './types';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Binding keys for the OpenAPI version transformer component.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
4
9
|
export namespace OpenApiVersionBindings {
|
|
10
|
+
/**
|
|
11
|
+
* Binding key for the component configuration.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
5
15
|
export const CONFIG = BindingKey.create<OpenApiVersionConfig>(
|
|
6
16
|
'openapi-version.config',
|
|
7
17
|
);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Binding key for the component itself.
|
|
21
|
+
*
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
8
24
|
export const COMPONENT = 'openapi-version.component';
|
|
9
25
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
Component,
|
|
4
|
-
createBindingFromClass,
|
|
5
|
-
} from '@loopback/core';
|
|
1
|
+
import {Binding, createBindingFromClass} from '@loopback/core';
|
|
2
|
+
import type {Component} from '@loopback/core';
|
|
6
3
|
import {asSpecEnhancer} from '@loopback/openapi-v3';
|
|
7
4
|
import {OpenApiVersionBindings} from './keys';
|
|
8
5
|
import {OpenApiVersionEnhancer} from './openapi-version.enhancer';
|
|
@@ -18,6 +15,8 @@ import {DEFAULT_CONFIG} from './types';
|
|
|
18
15
|
* app.component(OpenApiVersionComponent);
|
|
19
16
|
* app.bind(OpenApiVersionBindings.CONFIG).to({version: '3.1.0'});
|
|
20
17
|
* ```
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
21
20
|
*/
|
|
22
21
|
export class OpenApiVersionComponent implements Component {
|
|
23
22
|
bindings: Binding[] = [
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import {config, injectable} from '@loopback/core';
|
|
2
|
-
import {
|
|
3
|
-
OASEnhancer,
|
|
4
|
-
OpenApiSpec,
|
|
5
|
-
} from '@loopback/openapi-v3';
|
|
2
|
+
import {OASEnhancer, OpenApiSpec} from '@loopback/openapi-v3';
|
|
6
3
|
import debugFactory from 'debug';
|
|
7
4
|
import {OpenApiVersionBindings} from './keys';
|
|
8
5
|
import {transformOpenApiSpec} from './transform';
|
|
@@ -27,6 +24,8 @@ const debug = debugFactory('loopback:openapi-version');
|
|
|
27
24
|
* - 3.0: https://spec.openapis.org/oas/v3.0.3.html
|
|
28
25
|
* - 3.1: https://spec.openapis.org/oas/v3.1.0.html
|
|
29
26
|
* - 3.2: https://spec.openapis.org/oas/v3.2.0.html
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
30
29
|
*/
|
|
31
30
|
@injectable()
|
|
32
31
|
export class OpenApiVersionEnhancer implements OASEnhancer {
|
|
@@ -44,13 +43,13 @@ export class OpenApiVersionEnhancer implements OASEnhancer {
|
|
|
44
43
|
opts.version,
|
|
45
44
|
opts.transformNullable !== false,
|
|
46
45
|
);
|
|
47
|
-
const result = transformOpenApiSpec(
|
|
48
|
-
spec as Record<string, unknown>,
|
|
49
|
-
opts,
|
|
50
|
-
);
|
|
46
|
+
const result = transformOpenApiSpec(spec as Record<string, unknown>, opts);
|
|
51
47
|
|
|
52
48
|
if (result.warnings.length > 0) {
|
|
53
|
-
debug(
|
|
49
|
+
debug(
|
|
50
|
+
'emitted %d OpenAPI compatibility warnings',
|
|
51
|
+
result.warnings.length,
|
|
52
|
+
);
|
|
54
53
|
for (const w of result.warnings) {
|
|
55
54
|
debug('warning [%s]: %s', w.field, w.message);
|
|
56
55
|
}
|
package/src/transform.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {OpenApiTransformError, OpenApiVersionConfigError} from './errors';
|
|
2
|
+
import {DEFAULT_CONFIG} from './types';
|
|
3
|
+
import type {OpenApiVersionConfig} from './types';
|
|
2
4
|
|
|
3
5
|
// Internal type for untyped spec traversal.
|
|
4
6
|
interface Obj {
|
|
@@ -7,12 +9,16 @@ interface Obj {
|
|
|
7
9
|
|
|
8
10
|
const SUPPORTED_MINORS = [0, 1, 2] as const;
|
|
9
11
|
|
|
10
|
-
function isSupportedMinor(
|
|
12
|
+
function isSupportedMinor(
|
|
13
|
+
minor: number,
|
|
14
|
+
): minor is (typeof SUPPORTED_MINORS)[number] {
|
|
11
15
|
return (SUPPORTED_MINORS as readonly number[]).includes(minor);
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Diagnostic warning emitted when features are stripped during downgrade.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
16
22
|
*/
|
|
17
23
|
export interface TransformWarning {
|
|
18
24
|
field: string;
|
|
@@ -22,6 +28,8 @@ export interface TransformWarning {
|
|
|
22
28
|
/**
|
|
23
29
|
* Result of a spec transformation, including the transformed spec
|
|
24
30
|
* and any diagnostic warnings about lossy operations.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
25
33
|
*/
|
|
26
34
|
export interface TransformResult {
|
|
27
35
|
spec: Obj;
|
|
@@ -45,12 +53,19 @@ function warnOnce(
|
|
|
45
53
|
|
|
46
54
|
/**
|
|
47
55
|
* Parse and validate an OpenAPI version string.
|
|
48
|
-
* Throws if the version is not a supported
|
|
56
|
+
* Throws `OpenApiVersionConfigError` if the version is not a supported
|
|
57
|
+
* format.
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
49
60
|
*/
|
|
50
|
-
export function parseVersion(version: string): {
|
|
61
|
+
export function parseVersion(version: string): {
|
|
62
|
+
major: number;
|
|
63
|
+
minor: number;
|
|
64
|
+
patch: number;
|
|
65
|
+
} {
|
|
51
66
|
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
|
52
67
|
if (!match) {
|
|
53
|
-
throw new
|
|
68
|
+
throw new OpenApiVersionConfigError(
|
|
54
69
|
`Invalid OpenAPI version: "${version}". Expected format: "3.x.x"`,
|
|
55
70
|
);
|
|
56
71
|
}
|
|
@@ -59,12 +74,12 @@ export function parseVersion(version: string): {major: number; minor: number; pa
|
|
|
59
74
|
const patch = Number(match[3]);
|
|
60
75
|
|
|
61
76
|
if (major !== 3) {
|
|
62
|
-
throw new
|
|
77
|
+
throw new OpenApiVersionConfigError(
|
|
63
78
|
`Unsupported OpenAPI major version: ${major}. Only version 3.x is supported.`,
|
|
64
79
|
);
|
|
65
80
|
}
|
|
66
81
|
if (!isSupportedMinor(minor)) {
|
|
67
|
-
throw new
|
|
82
|
+
throw new OpenApiVersionConfigError(
|
|
68
83
|
`Unsupported OpenAPI minor version: 3.${minor}. Supported: 3.0.x, 3.1.x, 3.2.x`,
|
|
69
84
|
);
|
|
70
85
|
}
|
|
@@ -83,13 +98,15 @@ export function parseVersion(version: string): {major: number; minor: number; pa
|
|
|
83
98
|
* emitted for each stripped feature.
|
|
84
99
|
*
|
|
85
100
|
* Requires Node.js 18+ for structuredClone.
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
86
103
|
*/
|
|
87
104
|
export function transformOpenApiSpec(
|
|
88
105
|
spec: Obj,
|
|
89
106
|
config: OpenApiVersionConfig = DEFAULT_CONFIG,
|
|
90
107
|
): TransformResult {
|
|
91
108
|
if (typeof spec.openapi !== 'string') {
|
|
92
|
-
throw new
|
|
109
|
+
throw new OpenApiTransformError(
|
|
93
110
|
'Invalid OpenAPI spec: missing required string field "openapi".',
|
|
94
111
|
);
|
|
95
112
|
}
|
|
@@ -112,7 +129,11 @@ export function transformOpenApiSpec(
|
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
// 3.0 -> 3.1+: upgrade nullable
|
|
115
|
-
if (
|
|
132
|
+
if (
|
|
133
|
+
config.transformNullable !== false &&
|
|
134
|
+
targetMinor >= 1 &&
|
|
135
|
+
sourceMinor < 1
|
|
136
|
+
) {
|
|
116
137
|
upgradeNullable(out);
|
|
117
138
|
}
|
|
118
139
|
|
|
@@ -200,8 +221,11 @@ function downgradeNullable(
|
|
|
200
221
|
if (!Array.isArray(s[key])) continue;
|
|
201
222
|
const arr = s[key] as Obj[];
|
|
202
223
|
const nullIdx = arr.findIndex(
|
|
203
|
-
item =>
|
|
204
|
-
|
|
224
|
+
item =>
|
|
225
|
+
typeof item === 'object' &&
|
|
226
|
+
item !== null &&
|
|
227
|
+
Object.keys(item).length === 1 &&
|
|
228
|
+
item.type === 'null',
|
|
205
229
|
);
|
|
206
230
|
if (nullIdx !== -1) {
|
|
207
231
|
arr.splice(nullIdx, 1);
|
|
@@ -212,7 +236,12 @@ function downgradeNullable(
|
|
|
212
236
|
}
|
|
213
237
|
});
|
|
214
238
|
if (converted) {
|
|
215
|
-
warnOnce(
|
|
239
|
+
warnOnce(
|
|
240
|
+
warnings,
|
|
241
|
+
seen,
|
|
242
|
+
'nullable',
|
|
243
|
+
'Converted type arrays to nullable: true for 3.0 compatibility (lossy)',
|
|
244
|
+
);
|
|
216
245
|
}
|
|
217
246
|
}
|
|
218
247
|
|
|
@@ -227,18 +256,33 @@ function strip31Features(
|
|
|
227
256
|
): void {
|
|
228
257
|
if (spec.jsonSchemaDialect !== undefined) {
|
|
229
258
|
delete spec.jsonSchemaDialect;
|
|
230
|
-
warnOnce(
|
|
259
|
+
warnOnce(
|
|
260
|
+
warnings,
|
|
261
|
+
seen,
|
|
262
|
+
'jsonSchemaDialect',
|
|
263
|
+
'Removed jsonSchemaDialect because target version is 3.0.x',
|
|
264
|
+
);
|
|
231
265
|
}
|
|
232
266
|
|
|
233
267
|
if (spec.webhooks !== undefined) {
|
|
234
268
|
delete spec.webhooks;
|
|
235
|
-
warnOnce(
|
|
269
|
+
warnOnce(
|
|
270
|
+
warnings,
|
|
271
|
+
seen,
|
|
272
|
+
'webhooks',
|
|
273
|
+
'Removed webhooks because target version is 3.0.x (webhooks require 3.1+)',
|
|
274
|
+
);
|
|
236
275
|
}
|
|
237
276
|
|
|
238
277
|
const components = spec.components as Obj | undefined;
|
|
239
278
|
if (components?.pathItems !== undefined) {
|
|
240
279
|
delete components.pathItems;
|
|
241
|
-
warnOnce(
|
|
280
|
+
warnOnce(
|
|
281
|
+
warnings,
|
|
282
|
+
seen,
|
|
283
|
+
'components.pathItems',
|
|
284
|
+
'Removed components.pathItems because target version is 3.0.x',
|
|
285
|
+
);
|
|
242
286
|
}
|
|
243
287
|
|
|
244
288
|
const info = spec.info as Obj | undefined;
|
|
@@ -246,7 +290,12 @@ function strip31Features(
|
|
|
246
290
|
const license = info.license as Obj | undefined;
|
|
247
291
|
if (license?.identifier !== undefined) {
|
|
248
292
|
delete license.identifier;
|
|
249
|
-
warnOnce(
|
|
293
|
+
warnOnce(
|
|
294
|
+
warnings,
|
|
295
|
+
seen,
|
|
296
|
+
'info.license.identifier',
|
|
297
|
+
'Removed license identifier because target version is 3.0.x (use url instead)',
|
|
298
|
+
);
|
|
250
299
|
}
|
|
251
300
|
}
|
|
252
301
|
}
|
|
@@ -263,7 +312,12 @@ function strip32Features(
|
|
|
263
312
|
): void {
|
|
264
313
|
if (spec.$self !== undefined) {
|
|
265
314
|
delete spec.$self;
|
|
266
|
-
warnOnce(
|
|
315
|
+
warnOnce(
|
|
316
|
+
warnings,
|
|
317
|
+
seen,
|
|
318
|
+
'$self',
|
|
319
|
+
`Removed $self because target version is 3.${targetMinor}.x`,
|
|
320
|
+
);
|
|
267
321
|
}
|
|
268
322
|
|
|
269
323
|
// jsonSchemaDialect: only strip when targeting 3.0 (3.1 supports it)
|
|
@@ -276,7 +330,12 @@ function strip32Features(
|
|
|
276
330
|
const s = server as Obj;
|
|
277
331
|
if (s.name !== undefined) {
|
|
278
332
|
delete s.name;
|
|
279
|
-
warnOnce(
|
|
333
|
+
warnOnce(
|
|
334
|
+
warnings,
|
|
335
|
+
seen,
|
|
336
|
+
'servers',
|
|
337
|
+
`Removed server name because target version is 3.${targetMinor}.x`,
|
|
338
|
+
);
|
|
280
339
|
}
|
|
281
340
|
}
|
|
282
341
|
}
|
|
@@ -291,11 +350,21 @@ function strip32Features(
|
|
|
291
350
|
const pi = item as Obj;
|
|
292
351
|
if (pi.query !== undefined) {
|
|
293
352
|
delete pi.query;
|
|
294
|
-
warnOnce(
|
|
353
|
+
warnOnce(
|
|
354
|
+
warnings,
|
|
355
|
+
seen,
|
|
356
|
+
`paths.${path}.query`,
|
|
357
|
+
`Removed QUERY method because target version is 3.${targetMinor}.x`,
|
|
358
|
+
);
|
|
295
359
|
}
|
|
296
360
|
if (pi.additionalOperations !== undefined) {
|
|
297
361
|
delete pi.additionalOperations;
|
|
298
|
-
warnOnce(
|
|
362
|
+
warnOnce(
|
|
363
|
+
warnings,
|
|
364
|
+
seen,
|
|
365
|
+
`paths.${path}.additionalOperations`,
|
|
366
|
+
`Removed additionalOperations because target version is 3.${targetMinor}.x`,
|
|
367
|
+
);
|
|
299
368
|
}
|
|
300
369
|
}
|
|
301
370
|
}
|
|
@@ -309,11 +378,21 @@ function strip32Features(
|
|
|
309
378
|
const pi = item as Obj;
|
|
310
379
|
if (pi.query !== undefined) {
|
|
311
380
|
delete pi.query;
|
|
312
|
-
warnOnce(
|
|
381
|
+
warnOnce(
|
|
382
|
+
warnings,
|
|
383
|
+
seen,
|
|
384
|
+
`webhooks.${name}.query`,
|
|
385
|
+
`Removed QUERY method because target version is 3.${targetMinor}.x`,
|
|
386
|
+
);
|
|
313
387
|
}
|
|
314
388
|
if (pi.additionalOperations !== undefined) {
|
|
315
389
|
delete pi.additionalOperations;
|
|
316
|
-
warnOnce(
|
|
390
|
+
warnOnce(
|
|
391
|
+
warnings,
|
|
392
|
+
seen,
|
|
393
|
+
`webhooks.${name}.additionalOperations`,
|
|
394
|
+
`Removed additionalOperations because target version is 3.${targetMinor}.x`,
|
|
395
|
+
);
|
|
317
396
|
}
|
|
318
397
|
}
|
|
319
398
|
}
|
|
@@ -324,12 +403,21 @@ function strip32Features(
|
|
|
324
403
|
for (const tag of spec.tags) {
|
|
325
404
|
if (tag && typeof tag === 'object') {
|
|
326
405
|
const t = tag as Obj;
|
|
327
|
-
if (
|
|
406
|
+
if (
|
|
407
|
+
t.summary !== undefined ||
|
|
408
|
+
t.parent !== undefined ||
|
|
409
|
+
t.kind !== undefined
|
|
410
|
+
) {
|
|
328
411
|
delete t.summary;
|
|
329
412
|
delete t.parent;
|
|
330
413
|
delete t.kind;
|
|
331
414
|
if (!tagWarned) {
|
|
332
|
-
warnOnce(
|
|
415
|
+
warnOnce(
|
|
416
|
+
warnings,
|
|
417
|
+
seen,
|
|
418
|
+
'tags',
|
|
419
|
+
`Removed tag fields (summary, parent, kind) because target version is 3.${targetMinor}.x`,
|
|
420
|
+
);
|
|
333
421
|
tagWarned = true;
|
|
334
422
|
}
|
|
335
423
|
}
|
|
@@ -349,7 +437,12 @@ function strip32Features(
|
|
|
349
437
|
const flows = (scheme as Obj).flows as Obj | undefined;
|
|
350
438
|
if (flows?.device !== undefined) {
|
|
351
439
|
delete flows.device;
|
|
352
|
-
warnOnce(
|
|
440
|
+
warnOnce(
|
|
441
|
+
warnings,
|
|
442
|
+
seen,
|
|
443
|
+
`components.securitySchemes.${name}.flows.device`,
|
|
444
|
+
`Removed OAuth2 device flow because target version is 3.${targetMinor}.x`,
|
|
445
|
+
);
|
|
353
446
|
}
|
|
354
447
|
}
|
|
355
448
|
}
|
|
@@ -364,7 +457,12 @@ function strip32Features(
|
|
|
364
457
|
if (e.dataValue !== undefined || e.serializedValue !== undefined) {
|
|
365
458
|
delete e.dataValue;
|
|
366
459
|
delete e.serializedValue;
|
|
367
|
-
warnOnce(
|
|
460
|
+
warnOnce(
|
|
461
|
+
warnings,
|
|
462
|
+
seen,
|
|
463
|
+
`components.examples.${name}`,
|
|
464
|
+
`Removed dataValue/serializedValue because target version is 3.${targetMinor}.x`,
|
|
465
|
+
);
|
|
368
466
|
}
|
|
369
467
|
}
|
|
370
468
|
}
|
|
@@ -372,7 +470,12 @@ function strip32Features(
|
|
|
372
470
|
// 3.2 reusable media types
|
|
373
471
|
if (components.mediaTypes !== undefined) {
|
|
374
472
|
delete components.mediaTypes;
|
|
375
|
-
warnOnce(
|
|
473
|
+
warnOnce(
|
|
474
|
+
warnings,
|
|
475
|
+
seen,
|
|
476
|
+
'components.mediaTypes',
|
|
477
|
+
`Removed components.mediaTypes because target version is 3.${targetMinor}.x`,
|
|
478
|
+
);
|
|
376
479
|
}
|
|
377
480
|
}
|
|
378
481
|
|
|
@@ -383,7 +486,12 @@ function strip32Features(
|
|
|
383
486
|
const xml = schema.xml as Obj;
|
|
384
487
|
if (xml.text !== undefined) {
|
|
385
488
|
delete xml.text;
|
|
386
|
-
warnOnce(
|
|
489
|
+
warnOnce(
|
|
490
|
+
warnings,
|
|
491
|
+
seen,
|
|
492
|
+
'xml.text',
|
|
493
|
+
`Removed XML text field because target version is 3.${targetMinor}.x`,
|
|
494
|
+
);
|
|
387
495
|
}
|
|
388
496
|
}
|
|
389
497
|
});
|
|
@@ -396,7 +504,12 @@ function strip32Features(
|
|
|
396
504
|
if (p && typeof p === 'object' && !('$ref' in p)) {
|
|
397
505
|
if ((p as Obj).in === 'querystring') {
|
|
398
506
|
(p as Obj).in = 'query';
|
|
399
|
-
warnOnce(
|
|
507
|
+
warnOnce(
|
|
508
|
+
warnings,
|
|
509
|
+
seen,
|
|
510
|
+
`${opPath}.parameters`,
|
|
511
|
+
`Converted querystring parameter location to query because target version is 3.${targetMinor}.x`,
|
|
512
|
+
);
|
|
400
513
|
}
|
|
401
514
|
}
|
|
402
515
|
}
|
|
@@ -424,7 +537,12 @@ function stripMediaFields(
|
|
|
424
537
|
for (const field of fieldsToStrip) {
|
|
425
538
|
if (m[field] !== undefined) {
|
|
426
539
|
delete m[field];
|
|
427
|
-
warnOnce(
|
|
540
|
+
warnOnce(
|
|
541
|
+
warnings,
|
|
542
|
+
seen,
|
|
543
|
+
`${location}.${mt}.${field}`,
|
|
544
|
+
`Removed ${field} because target version is 3.${targetMinor}.x`,
|
|
545
|
+
);
|
|
428
546
|
}
|
|
429
547
|
}
|
|
430
548
|
}
|
|
@@ -444,7 +562,8 @@ function stripMediaFields(
|
|
|
444
562
|
const resp = responses[code];
|
|
445
563
|
if (!resp || typeof resp !== 'object' || '$ref' in resp) continue;
|
|
446
564
|
const content = (resp as Obj).content as Obj | undefined;
|
|
447
|
-
if (content)
|
|
565
|
+
if (content)
|
|
566
|
+
stripFromContent(content, `${opPath}.responses.${code}.content`);
|
|
448
567
|
}
|
|
449
568
|
}
|
|
450
569
|
}
|
|
@@ -488,7 +607,11 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
488
607
|
}
|
|
489
608
|
|
|
490
609
|
// items
|
|
491
|
-
if (
|
|
610
|
+
if (
|
|
611
|
+
schema.items &&
|
|
612
|
+
typeof schema.items === 'object' &&
|
|
613
|
+
!('$ref' in schema.items)
|
|
614
|
+
) {
|
|
492
615
|
visit(schema.items as Obj);
|
|
493
616
|
}
|
|
494
617
|
|
|
@@ -496,13 +619,18 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
496
619
|
for (const key of ['allOf', 'oneOf', 'anyOf', 'prefixItems']) {
|
|
497
620
|
if (Array.isArray(schema[key])) {
|
|
498
621
|
for (const item of schema[key] as Obj[]) {
|
|
499
|
-
if (item && typeof item === 'object' && !('$ref' in item))
|
|
622
|
+
if (item && typeof item === 'object' && !('$ref' in item))
|
|
623
|
+
visit(item);
|
|
500
624
|
}
|
|
501
625
|
}
|
|
502
626
|
}
|
|
503
627
|
|
|
504
628
|
// not
|
|
505
|
-
if (
|
|
629
|
+
if (
|
|
630
|
+
schema.not &&
|
|
631
|
+
typeof schema.not === 'object' &&
|
|
632
|
+
!('$ref' in schema.not)
|
|
633
|
+
) {
|
|
506
634
|
visit(schema.not as Obj);
|
|
507
635
|
}
|
|
508
636
|
|
|
@@ -520,10 +648,18 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
520
648
|
for (const mt in content) {
|
|
521
649
|
const media = content[mt] as Obj | undefined;
|
|
522
650
|
if (!media || typeof media !== 'object') continue;
|
|
523
|
-
if (
|
|
651
|
+
if (
|
|
652
|
+
media.schema &&
|
|
653
|
+
typeof media.schema === 'object' &&
|
|
654
|
+
!('$ref' in media.schema)
|
|
655
|
+
) {
|
|
524
656
|
visit(media.schema as Obj);
|
|
525
657
|
}
|
|
526
|
-
if (
|
|
658
|
+
if (
|
|
659
|
+
media.itemSchema &&
|
|
660
|
+
typeof media.itemSchema === 'object' &&
|
|
661
|
+
!('$ref' in media.itemSchema)
|
|
662
|
+
) {
|
|
527
663
|
visit(media.itemSchema as Obj);
|
|
528
664
|
}
|
|
529
665
|
}
|
|
@@ -533,7 +669,11 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
533
669
|
for (const p of params) {
|
|
534
670
|
if (!p || typeof p !== 'object' || '$ref' in p) continue;
|
|
535
671
|
const param = p as Obj;
|
|
536
|
-
if (
|
|
672
|
+
if (
|
|
673
|
+
param.schema &&
|
|
674
|
+
typeof param.schema === 'object' &&
|
|
675
|
+
!('$ref' in param.schema)
|
|
676
|
+
) {
|
|
537
677
|
visit(param.schema as Obj);
|
|
538
678
|
}
|
|
539
679
|
if (param.content && typeof param.content === 'object') {
|
|
@@ -559,7 +699,11 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
559
699
|
const p = params[name];
|
|
560
700
|
if (p && typeof p === 'object' && !('$ref' in p)) {
|
|
561
701
|
const param = p as Obj;
|
|
562
|
-
if (
|
|
702
|
+
if (
|
|
703
|
+
param.schema &&
|
|
704
|
+
typeof param.schema === 'object' &&
|
|
705
|
+
!('$ref' in param.schema)
|
|
706
|
+
) {
|
|
563
707
|
visit(param.schema as Obj);
|
|
564
708
|
}
|
|
565
709
|
}
|
|
@@ -592,7 +736,11 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
592
736
|
const h = headers[name];
|
|
593
737
|
if (!h || typeof h !== 'object' || '$ref' in h) continue;
|
|
594
738
|
const header = h as Obj;
|
|
595
|
-
if (
|
|
739
|
+
if (
|
|
740
|
+
header.schema &&
|
|
741
|
+
typeof header.schema === 'object' &&
|
|
742
|
+
!('$ref' in header.schema)
|
|
743
|
+
) {
|
|
596
744
|
visit(header.schema as Obj);
|
|
597
745
|
}
|
|
598
746
|
}
|
|
@@ -610,7 +758,17 @@ function walkAllSchemas(spec: Obj, visitor: (schema: Obj) => void): void {
|
|
|
610
758
|
visitParams(pi.parameters);
|
|
611
759
|
}
|
|
612
760
|
|
|
613
|
-
const verbs = [
|
|
761
|
+
const verbs = [
|
|
762
|
+
'get',
|
|
763
|
+
'post',
|
|
764
|
+
'put',
|
|
765
|
+
'patch',
|
|
766
|
+
'delete',
|
|
767
|
+
'options',
|
|
768
|
+
'head',
|
|
769
|
+
'trace',
|
|
770
|
+
'query',
|
|
771
|
+
];
|
|
614
772
|
for (const verb of verbs) {
|
|
615
773
|
const op = pi[verb];
|
|
616
774
|
if (!op || typeof op !== 'object') continue;
|
|
@@ -666,7 +824,17 @@ function forEachOperation(
|
|
|
666
824
|
spec: Obj,
|
|
667
825
|
visitor: (op: Obj, path: string) => void,
|
|
668
826
|
): void {
|
|
669
|
-
const verbs = [
|
|
827
|
+
const verbs = [
|
|
828
|
+
'get',
|
|
829
|
+
'post',
|
|
830
|
+
'put',
|
|
831
|
+
'patch',
|
|
832
|
+
'delete',
|
|
833
|
+
'options',
|
|
834
|
+
'head',
|
|
835
|
+
'trace',
|
|
836
|
+
'query',
|
|
837
|
+
];
|
|
670
838
|
|
|
671
839
|
function visitPathItems(pathsObj: Obj, prefix: string): void {
|
|
672
840
|
for (const path in pathsObj) {
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported OpenAPI Specification target versions.
|
|
3
|
+
*
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
1
6
|
export type OpenApiVersion = '3.0.0' | '3.1.0' | '3.2.0';
|
|
2
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for the OpenAPI version transformer.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
3
13
|
export interface OpenApiVersionConfig {
|
|
4
14
|
/**
|
|
5
15
|
* Target OpenAPI Specification version.
|
|
@@ -14,6 +24,12 @@ export interface OpenApiVersionConfig {
|
|
|
14
24
|
transformNullable?: boolean;
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Default configuration applied when no `OpenApiVersionConfig` binding
|
|
29
|
+
* is provided. Targets 3.0.0 with nullable transformation enabled.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
17
33
|
export const DEFAULT_CONFIG: OpenApiVersionConfig = {
|
|
18
34
|
version: '3.0.0',
|
|
19
35
|
transformNullable: true,
|