@kubb/adapter-oas 5.0.0-alpha.1 → 5.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +432 -220
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +71 -27
- package/dist/index.js +433 -221
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/adapter.ts +28 -8
- package/src/constants.ts +19 -9
- package/src/oas/Oas.ts +16 -9
- package/src/oas/types.ts +19 -0
- package/src/oas/utils.ts +1 -1
- package/src/parser.ts +209 -228
- package/src/types.ts +41 -19
- package/src/utils.ts +168 -0
package/src/parser.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
import { pascalCase } from '@internals/utils'
|
|
2
|
-
import {
|
|
3
|
-
collect,
|
|
4
|
-
createOperation,
|
|
5
|
-
createParameter,
|
|
6
|
-
createProperty,
|
|
7
|
-
createResponse,
|
|
8
|
-
createRoot,
|
|
9
|
-
createSchema,
|
|
10
|
-
narrowSchema,
|
|
11
|
-
schemaTypes,
|
|
12
|
-
transform,
|
|
13
|
-
} from '@kubb/ast'
|
|
1
|
+
import { getUniqueName, pascalCase, URLPath } from '@internals/utils'
|
|
2
|
+
import { createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, narrowSchema, schemaTypes, transform } from '@kubb/ast'
|
|
14
3
|
import type {
|
|
15
4
|
ArraySchemaNode,
|
|
16
5
|
DateSchemaNode,
|
|
@@ -38,11 +27,12 @@ import type {
|
|
|
38
27
|
TimeSchemaNode,
|
|
39
28
|
UnionSchemaNode,
|
|
40
29
|
} from '@kubb/ast/types'
|
|
41
|
-
import
|
|
42
|
-
import { enumExtensionKeys, formatMap, knownMediaTypes } from './constants.ts'
|
|
30
|
+
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, formatMap, knownMediaTypes } from './constants.ts'
|
|
43
31
|
import type { Oas } from './oas/Oas.ts'
|
|
44
|
-
import type { contentType, Operation, SchemaObject } from './oas/types.ts'
|
|
32
|
+
import type { contentType, Operation, ReferenceObject, SchemaObject } from './oas/types.ts'
|
|
45
33
|
import { flattenSchema, isDiscriminator, isNullable, isReference } from './oas/utils.ts'
|
|
34
|
+
import type { ParserOptions } from './types.ts'
|
|
35
|
+
import { applyDiscriminatorEnum, extractRefName, mergeAdjacentAnonymousObjects, simplifyUnionMembers } from './utils.ts'
|
|
46
36
|
|
|
47
37
|
/**
|
|
48
38
|
* Distributive `Omit` — correctly distributes over union types so that
|
|
@@ -65,14 +55,16 @@ type DateTimeNodeByDateType = {
|
|
|
65
55
|
/**
|
|
66
56
|
* Resolves the AST node produced by `format: 'date-time'` based on the `dateType` option.
|
|
67
57
|
*/
|
|
68
|
-
type ResolveDateTimeNode<TDateType extends
|
|
58
|
+
type ResolveDateTimeNode<TDateType extends ParserOptions['dateType']> = DateTimeNodeByDateType[TDateType extends keyof DateTimeNodeByDateType
|
|
59
|
+
? TDateType
|
|
60
|
+
: 'string']
|
|
69
61
|
|
|
70
62
|
/**
|
|
71
63
|
* Single source of truth: ordered list of `[shape, SchemaNode]` pairs.
|
|
72
64
|
* `InferSchemaNode` walks this tuple in order and returns the node type of the first matching entry.
|
|
73
65
|
* Parameterized over `TDateType` so `format: 'date-time'` resolves to the correct node based on the option.
|
|
74
66
|
*/
|
|
75
|
-
type SchemaNodeMap<TDateType extends
|
|
67
|
+
type SchemaNodeMap<TDateType extends ParserOptions['dateType'] = 'string'> = [
|
|
76
68
|
[{ $ref: string }, RefSchemaNode],
|
|
77
69
|
// allOf with sibling `properties` always produces an intersection (shared props are appended as a member).
|
|
78
70
|
[{ allOf: ReadonlyArray<unknown>; properties: object }, IntersectionSchemaNode],
|
|
@@ -116,7 +108,7 @@ type SchemaNodeMap<TDateType extends Options['dateType'] = Options['dateType']>
|
|
|
116
108
|
|
|
117
109
|
export type InferSchemaNode<
|
|
118
110
|
TSchema extends SchemaObject,
|
|
119
|
-
TDateType extends
|
|
111
|
+
TDateType extends ParserOptions['dateType'] = 'string',
|
|
120
112
|
TEntries extends ReadonlyArray<[object, SchemaNode]> = SchemaNodeMap<TDateType>,
|
|
121
113
|
> = TEntries extends [infer TEntry extends [object, SchemaNode], ...infer TRest extends ReadonlyArray<[object, SchemaNode]>]
|
|
122
114
|
? TSchema extends TEntry[0]
|
|
@@ -124,32 +116,6 @@ export type InferSchemaNode<
|
|
|
124
116
|
: InferSchemaNode<TSchema, TDateType, TRest>
|
|
125
117
|
: SchemaNode
|
|
126
118
|
|
|
127
|
-
/**
|
|
128
|
-
* Controls how various OAS constructs are mapped to Kubb AST nodes.
|
|
129
|
-
*/
|
|
130
|
-
export type Options = {
|
|
131
|
-
/**
|
|
132
|
-
* How `format: 'date-time'` schemas are represented. `false` falls through to a plain string.
|
|
133
|
-
*/
|
|
134
|
-
dateType: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
|
|
135
|
-
/**
|
|
136
|
-
* Whether `type: 'integer'` and `format: 'int64'` produce `number` or `bigint` nodes.
|
|
137
|
-
*/
|
|
138
|
-
integerType?: 'number' | 'bigint'
|
|
139
|
-
/**
|
|
140
|
-
* AST type used when no schema type can be inferred.
|
|
141
|
-
*/
|
|
142
|
-
unknownType: 'any' | 'unknown' | 'void'
|
|
143
|
-
/**
|
|
144
|
-
* AST type used for completely empty schemas (`{}`).
|
|
145
|
-
*/
|
|
146
|
-
emptySchemaType: 'any' | 'unknown' | 'void'
|
|
147
|
-
/**
|
|
148
|
-
* Suffix appended to derived enum names when building property schema names.
|
|
149
|
-
*/
|
|
150
|
-
enumSuffix: string
|
|
151
|
-
}
|
|
152
|
-
|
|
153
119
|
/**
|
|
154
120
|
* Construction-time options for `createOasParser`.
|
|
155
121
|
*/
|
|
@@ -158,17 +124,6 @@ export type OasParserOptions = {
|
|
|
158
124
|
collisionDetection?: boolean
|
|
159
125
|
}
|
|
160
126
|
|
|
161
|
-
/**
|
|
162
|
-
* Default values for all `Options` fields.
|
|
163
|
-
*/
|
|
164
|
-
const DEFAULT_OPTIONS = {
|
|
165
|
-
dateType: 'string',
|
|
166
|
-
integerType: 'number',
|
|
167
|
-
unknownType: 'any',
|
|
168
|
-
emptySchemaType: 'any',
|
|
169
|
-
enumSuffix: 'enum',
|
|
170
|
-
} as const satisfies Options
|
|
171
|
-
|
|
172
127
|
/**
|
|
173
128
|
* Looks up the Kubb `SchemaType` for a given OAS `format` string.
|
|
174
129
|
* Returns `undefined` for formats not in `formatMap` (e.g. `int64`, `date-time`),
|
|
@@ -178,15 +133,6 @@ function formatToSchemaType(format: string): SchemaType | undefined {
|
|
|
178
133
|
return formatMap[format as keyof typeof formatMap]
|
|
179
134
|
}
|
|
180
135
|
|
|
181
|
-
/**
|
|
182
|
-
* Extracts the final path segment of a JSON Pointer `$ref` string.
|
|
183
|
-
* For `#/components/schemas/Order` this returns `'Order'`.
|
|
184
|
-
* Falls back to the full ref string when no slash is present.
|
|
185
|
-
*/
|
|
186
|
-
function extractRefName($ref: string): string {
|
|
187
|
-
return $ref.split('/').at(-1) ?? $ref
|
|
188
|
-
}
|
|
189
|
-
|
|
190
136
|
/**
|
|
191
137
|
* Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
192
138
|
* Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
|
|
@@ -203,7 +149,7 @@ function getPrimitiveType(type: string | undefined): PrimitiveSchemaType {
|
|
|
203
149
|
* Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
|
|
204
150
|
*/
|
|
205
151
|
function toMediaType(contentType: string): MediaType | undefined {
|
|
206
|
-
return knownMediaTypes.
|
|
152
|
+
return knownMediaTypes.has(contentType as MediaType) ? (contentType as MediaType) : undefined
|
|
207
153
|
}
|
|
208
154
|
|
|
209
155
|
/**
|
|
@@ -219,8 +165,8 @@ type SchemaContext = {
|
|
|
219
165
|
* Normalized single type string (first element when OAS 3.1 multi-type array).
|
|
220
166
|
*/
|
|
221
167
|
type: string | undefined
|
|
222
|
-
options: Partial<
|
|
223
|
-
mergedOptions:
|
|
168
|
+
options: Partial<ParserOptions> | undefined
|
|
169
|
+
mergedOptions: ParserOptions
|
|
224
170
|
}
|
|
225
171
|
|
|
226
172
|
/**
|
|
@@ -231,11 +177,11 @@ export type OasParser = {
|
|
|
231
177
|
* Converts an OpenAPI/Swagger spec (wrapped in a Kubb `Oas` instance) into
|
|
232
178
|
* a `RootNode` — the top-level node of the `@kubb/ast` tree.
|
|
233
179
|
*/
|
|
234
|
-
parse: <TOptions extends Partial<
|
|
235
|
-
convertSchema: <TFormat extends string, TSchema extends SchemaObject & { format?: TFormat }, TOptions extends Partial<
|
|
180
|
+
parse: <TOptions extends Partial<ParserOptions> = object>(options?: TOptions) => RootNode
|
|
181
|
+
convertSchema: <TFormat extends string, TSchema extends SchemaObject & { format?: TFormat }, TOptions extends Partial<ParserOptions> = object>(
|
|
236
182
|
params: { schema: TSchema; name?: string },
|
|
237
183
|
options?: TOptions,
|
|
238
|
-
) => InferSchemaNode<TSchema, TOptions extends { dateType:
|
|
184
|
+
) => InferSchemaNode<TSchema, TOptions extends { dateType: ParserOptions['dateType'] } ? TOptions['dateType'] : 'string'>
|
|
239
185
|
/**
|
|
240
186
|
* Walks `node` and replaces each `ref` value with the name returned by
|
|
241
187
|
* `resolveName`. The callback receives the full `$ref` path (e.g. `#/components/schemas/Order`)
|
|
@@ -245,45 +191,15 @@ export type OasParser = {
|
|
|
245
191
|
* the transformed name to use (e.g. with a plugin `transformers.name` applied).
|
|
246
192
|
*/
|
|
247
193
|
resolveRefs: (node: SchemaNode, resolveName: (ref: string) => string | undefined, resolveEnumName?: (name: string) => string | undefined) => SchemaNode
|
|
194
|
+
|
|
248
195
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* to a known component in the OAS spec (`oas.get($ref)` is truthy).
|
|
196
|
+
* Map from original `$ref` paths to their collision-resolved schema names.
|
|
197
|
+
* e.g. `'#/components/schemas/Order'` → `'OrderSchema'`
|
|
252
198
|
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* `{ name, path }` pair for the generated import, or `undefined` to skip it.
|
|
256
|
-
*
|
|
257
|
-
* @example
|
|
258
|
-
* ```ts
|
|
259
|
-
* const imports = parser.getImports(schemaNode, (schemaName) => ({
|
|
260
|
-
* name: schemaManager.getName(schemaName, { type: 'type' }),
|
|
261
|
-
* path: schemaManager.getFile(schemaName).path,
|
|
262
|
-
* }))
|
|
263
|
-
* ```
|
|
199
|
+
* Pass this to the standalone `getImports()` to resolve imports without holding
|
|
200
|
+
* a reference to the full parser or OAS instance.
|
|
264
201
|
*/
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* When a discriminator is present, replaces the discriminator property's schema with
|
|
270
|
-
* an enum of the mapping keys so downstream code emits a precise literal-union type.
|
|
271
|
-
* Returns the original schema unchanged when there is no discriminator or no mapping.
|
|
272
|
-
*/
|
|
273
|
-
function applyDiscriminatorEnum(schema: SchemaObject): SchemaObject {
|
|
274
|
-
if (!isDiscriminator(schema)) return schema
|
|
275
|
-
const propName = schema.discriminator.propertyName
|
|
276
|
-
if (!schema.properties?.[propName]) return schema
|
|
277
|
-
return {
|
|
278
|
-
...schema,
|
|
279
|
-
properties: {
|
|
280
|
-
...schema.properties,
|
|
281
|
-
[propName]: {
|
|
282
|
-
...(schema.properties[propName] as SchemaObject),
|
|
283
|
-
enum: schema.discriminator.mapping ? Object.keys(schema.discriminator.mapping) : undefined,
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
} as SchemaObject
|
|
202
|
+
nameMapping: Map<string, string>
|
|
287
203
|
}
|
|
288
204
|
|
|
289
205
|
/**
|
|
@@ -310,6 +226,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
310
226
|
// e.g., { '#/components/schemas/Order': 'OrderSchema', '#/components/responses/Product': 'ProductResponse' }
|
|
311
227
|
const { schemas: schemaObjects, nameMapping } = oas.getSchemas({ contentType, collisionDetection })
|
|
312
228
|
|
|
229
|
+
// Legacy enum name deduplication: tracks used enum names and appends numeric suffixes
|
|
230
|
+
// (e.g. ParamsStatusEnum, ParamsStatusEnum2) when collisionDetection is disabled.
|
|
231
|
+
const usedEnumNames: Record<string, number> = {}
|
|
232
|
+
|
|
233
|
+
// Only apply legacy naming when collisionDetection is explicitly false.
|
|
234
|
+
// When undefined (e.g. direct parser usage without adapter), use the default (new) behavior.
|
|
235
|
+
const isLegacyNaming = collisionDetection === false
|
|
236
|
+
|
|
313
237
|
/**
|
|
314
238
|
* Maps an `'any' | 'unknown' | 'void'` option string to the corresponding `SchemaType` constant.
|
|
315
239
|
* Used for both `unknownType` (unannotated schemas) and `emptySchemaType` (empty `{}` schemas).
|
|
@@ -325,7 +249,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
325
249
|
* Returns `undefined` when `dateType` is `false`, meaning the format should fall through to `string`.
|
|
326
250
|
*/
|
|
327
251
|
function getDateType(
|
|
328
|
-
options:
|
|
252
|
+
options: ParserOptions,
|
|
329
253
|
format: 'date-time' | 'date' | 'time',
|
|
330
254
|
): { type: 'datetime'; offset?: boolean; local?: boolean } | { type: 'date' | 'time'; representation: 'date' | 'string' } | undefined {
|
|
331
255
|
if (!options.dateType) {
|
|
@@ -357,7 +281,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
357
281
|
* Shared metadata fields included in every `createSchema` call.
|
|
358
282
|
* Centralizes the common properties so sub-handlers don't repeat them.
|
|
359
283
|
*/
|
|
360
|
-
function
|
|
284
|
+
function renderSchemaBase(schema: SchemaObject, name: string | undefined, nullable: true | undefined, defaultValue: unknown) {
|
|
361
285
|
return {
|
|
362
286
|
name,
|
|
363
287
|
nullable,
|
|
@@ -383,18 +307,17 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
383
307
|
* reflected in generated JSDoc and type modifiers.
|
|
384
308
|
*/
|
|
385
309
|
function convertRef({ schema, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
386
|
-
const schemaObject = schema as unknown as SchemaObject & { $ref: string }
|
|
387
310
|
return createSchema({
|
|
388
311
|
type: 'ref',
|
|
389
|
-
name: extractRefName(
|
|
390
|
-
ref:
|
|
312
|
+
name: extractRefName(schema.$ref!),
|
|
313
|
+
ref: schema.$ref,
|
|
391
314
|
nullable,
|
|
392
|
-
description:
|
|
393
|
-
deprecated:
|
|
394
|
-
readOnly:
|
|
395
|
-
writeOnly:
|
|
396
|
-
pattern:
|
|
397
|
-
example:
|
|
315
|
+
description: schema.description,
|
|
316
|
+
deprecated: schema.deprecated,
|
|
317
|
+
readOnly: schema.readOnly,
|
|
318
|
+
writeOnly: schema.writeOnly,
|
|
319
|
+
pattern: schema.type === 'string' ? schema.pattern : undefined,
|
|
320
|
+
example: schema.example,
|
|
398
321
|
default: defaultValue,
|
|
399
322
|
})
|
|
400
323
|
}
|
|
@@ -421,8 +344,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
421
344
|
!(Array.isArray(schema.required) && schema.required.length) &&
|
|
422
345
|
schema.additionalProperties === undefined
|
|
423
346
|
) {
|
|
424
|
-
const [memberSchema] = schema.allOf as SchemaObject
|
|
425
|
-
const memberNode = convertSchema({ schema: memberSchema! }, options)
|
|
347
|
+
const [memberSchema] = schema.allOf as Array<SchemaObject | ReferenceObject>
|
|
348
|
+
const memberNode = convertSchema({ schema: memberSchema! as SchemaObject }, options)
|
|
426
349
|
const { kind: _kind, ...memberNodeProps } = memberNode
|
|
427
350
|
const mergedNullable = nullable || memberNode.nullable || undefined
|
|
428
351
|
const mergedDefault = schema.default === null && mergedNullable ? undefined : (schema.default ?? memberNode.default)
|
|
@@ -444,22 +367,24 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
444
367
|
|
|
445
368
|
// When a child schema extends a discriminator parent via allOf and the parent's oneOf/anyOf
|
|
446
369
|
// references that child back, skip that allOf item to prevent a circular type reference.
|
|
447
|
-
const allOfMembers: SchemaNode
|
|
370
|
+
const allOfMembers: Array<SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
|
|
448
371
|
.filter((item) => {
|
|
449
372
|
if (!isReference(item) || !name) return true
|
|
450
|
-
const deref = oas.get<SchemaObject>(
|
|
373
|
+
const deref = oas.get<SchemaObject>(item.$ref)
|
|
451
374
|
if (!deref || !isDiscriminator(deref)) return true
|
|
452
|
-
const parentUnion =
|
|
375
|
+
const parentUnion = deref.oneOf ?? deref.anyOf
|
|
453
376
|
if (!parentUnion) return true
|
|
454
377
|
const childRef = `#/components/schemas/${name}`
|
|
455
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) &&
|
|
456
|
-
const inMapping = Object.values(
|
|
457
|
-
(v) => v === childRef,
|
|
458
|
-
)
|
|
378
|
+
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
379
|
+
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
459
380
|
return !inOneOf && !inMapping
|
|
460
381
|
})
|
|
461
382
|
.map((s) => convertSchema({ schema: s as SchemaObject }, options))
|
|
462
383
|
|
|
384
|
+
// Track where allOf-derived members end so only the synthetic members added below
|
|
385
|
+
// (injected required-key objects + outer-properties object) are candidates for merging.
|
|
386
|
+
const syntheticStart = allOfMembers.length
|
|
387
|
+
|
|
463
388
|
// When `required` lists keys not present in the outer `properties`, resolve them from
|
|
464
389
|
// the allOf member schemas and inject them as extra intersection members.
|
|
465
390
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
@@ -467,10 +392,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
467
392
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key))
|
|
468
393
|
|
|
469
394
|
if (missingRequired.length) {
|
|
470
|
-
const resolvedMembers = (schema.allOf as SchemaObject
|
|
471
|
-
if (!isReference(item)) return [item]
|
|
395
|
+
const resolvedMembers = (schema.allOf as Array<SchemaObject | ReferenceObject>).flatMap((item) => {
|
|
396
|
+
if (!isReference(item)) return [item as SchemaObject]
|
|
472
397
|
const deref = oas.get<SchemaObject>(item.$ref)
|
|
473
|
-
return deref && !isReference(deref) ? [deref
|
|
398
|
+
return deref && !isReference(deref) ? [deref] : []
|
|
474
399
|
})
|
|
475
400
|
|
|
476
401
|
for (const key of missingRequired) {
|
|
@@ -485,14 +410,15 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
485
410
|
}
|
|
486
411
|
|
|
487
412
|
if (schema.properties) {
|
|
488
|
-
const { allOf: _allOf, ...schemaWithoutAllOf } = schema
|
|
489
|
-
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf
|
|
413
|
+
const { allOf: _allOf, ...schemaWithoutAllOf } = schema
|
|
414
|
+
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf }, options))
|
|
490
415
|
}
|
|
491
416
|
|
|
417
|
+
// Merge consecutive anonymous object members within the synthetic portion — see `mergeAdjacentAnonymousObjects`.
|
|
492
418
|
return createSchema({
|
|
493
419
|
type: 'intersection',
|
|
494
|
-
members: allOfMembers,
|
|
495
|
-
...
|
|
420
|
+
members: [...allOfMembers.slice(0, syntheticStart), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
|
|
421
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
496
422
|
})
|
|
497
423
|
}
|
|
498
424
|
|
|
@@ -507,30 +433,48 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
507
433
|
function convertUnion({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
508
434
|
const unionMembers = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])]
|
|
509
435
|
const unionBase = {
|
|
510
|
-
...
|
|
436
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
511
437
|
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
512
438
|
}
|
|
513
439
|
|
|
514
440
|
if (schema.properties) {
|
|
515
|
-
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
516
|
-
const
|
|
441
|
+
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
442
|
+
const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
|
|
443
|
+
|
|
444
|
+
// Strip discriminator so convertObject won't re-apply the full mapping enum.
|
|
445
|
+
const memberBaseSchema: SchemaObject = discriminator
|
|
446
|
+
? (Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== 'discriminator')) as SchemaObject)
|
|
447
|
+
: schemaWithoutUnion
|
|
448
|
+
|
|
449
|
+
// Convert shared properties once to avoid duplicate enum naming
|
|
450
|
+
// (e.g. StatusEnum appearing twice and getting a numeric suffix).
|
|
451
|
+
const sharedPropertiesNode = convertSchema({ schema: memberBaseSchema, name: isLegacyNaming ? undefined : name }, options)
|
|
517
452
|
|
|
518
453
|
return createSchema({
|
|
519
454
|
type: 'union',
|
|
520
455
|
...unionBase,
|
|
521
|
-
members: unionMembers.map((s) =>
|
|
522
|
-
|
|
456
|
+
members: unionMembers.map((s) => {
|
|
457
|
+
const ref = isReference(s) ? s.$ref : undefined
|
|
458
|
+
const discriminatorValue = discriminator?.mapping && ref ? Object.entries(discriminator.mapping).find(([, v]) => v === ref)?.[0] : undefined
|
|
459
|
+
|
|
460
|
+
let propertiesNode = sharedPropertiesNode
|
|
461
|
+
|
|
462
|
+
if (discriminatorValue && discriminator) {
|
|
463
|
+
propertiesNode = applyDiscriminatorEnum({ node: propertiesNode, propertyName: discriminator.propertyName, values: [discriminatorValue] })
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return createSchema({
|
|
523
467
|
type: 'intersection',
|
|
524
468
|
members: [convertSchema({ schema: s as SchemaObject }, options), propertiesNode],
|
|
525
|
-
})
|
|
526
|
-
),
|
|
469
|
+
})
|
|
470
|
+
}),
|
|
527
471
|
})
|
|
528
472
|
}
|
|
529
473
|
|
|
530
474
|
return createSchema({
|
|
531
475
|
type: 'union',
|
|
532
476
|
...unionBase,
|
|
533
|
-
members: unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options)),
|
|
477
|
+
members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options))),
|
|
534
478
|
})
|
|
535
479
|
}
|
|
536
480
|
|
|
@@ -559,7 +503,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
559
503
|
type: 'enum',
|
|
560
504
|
primitive: constPrimitive,
|
|
561
505
|
enumValues: [constValue as string | number | boolean],
|
|
562
|
-
...
|
|
506
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
563
507
|
})
|
|
564
508
|
}
|
|
565
509
|
|
|
@@ -569,7 +513,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
569
513
|
* (i.e. `format: 'date-time'` with `dateType: false`).
|
|
570
514
|
*/
|
|
571
515
|
function convertFormat({ schema, name, nullable, defaultValue, mergedOptions }: SchemaContext): SchemaNode | undefined {
|
|
572
|
-
const base =
|
|
516
|
+
const base = renderSchemaBase(schema, name, nullable, defaultValue)
|
|
573
517
|
|
|
574
518
|
// int64 is option-dependent so it can't live in the static formatMap.
|
|
575
519
|
if (schema.format === 'int64') {
|
|
@@ -603,6 +547,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
603
547
|
if (specialType === 'number' || specialType === 'integer' || specialType === 'bigint') {
|
|
604
548
|
return createSchema({ ...base, primitive: specialPrimitive, type: specialType })
|
|
605
549
|
}
|
|
550
|
+
if (specialType === 'url') {
|
|
551
|
+
return createSchema({ ...base, primitive: 'string' as const, type: 'url' })
|
|
552
|
+
}
|
|
553
|
+
|
|
606
554
|
return createSchema({ ...base, primitive: specialPrimitive, type: specialType as ScalarSchemaType })
|
|
607
555
|
}
|
|
608
556
|
|
|
@@ -619,10 +567,9 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
619
567
|
function convertEnum({ schema, name, nullable, type, options }: SchemaContext): SchemaNode {
|
|
620
568
|
// Malformed schema: `{ type: 'array', enum: [...] }` — normalize by moving the enum into items.
|
|
621
569
|
if (type === 'array') {
|
|
622
|
-
const
|
|
623
|
-
const
|
|
624
|
-
const
|
|
625
|
-
const { enum: _enum, ...schemaWithoutEnum } = schema as SchemaObject & { enum?: unknown[] }
|
|
570
|
+
const isItemsObject = typeof schema.items === 'object' && !Array.isArray(schema.items)
|
|
571
|
+
const normalizedItems: SchemaObject = { ...(isItemsObject ? (schema.items as SchemaObject) : {}), enum: schema.enum }
|
|
572
|
+
const { enum: _enum, ...schemaWithoutEnum } = schema
|
|
626
573
|
return convertSchema({ schema: { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject, name }, options)
|
|
627
574
|
}
|
|
628
575
|
|
|
@@ -715,22 +662,69 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
715
662
|
* - not required + not nullable → `optional: true`
|
|
716
663
|
* - not required + nullable → `nullish: true`
|
|
717
664
|
*/
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
665
|
+
/**
|
|
666
|
+
* Builds the propagation name for a child property during recursive schema conversion.
|
|
667
|
+
*
|
|
668
|
+
* - **Legacy naming** (`isLegacyNaming`): only the immediate property key is used
|
|
669
|
+
* (e.g. `Params` for property `params`), keeping nested names short.
|
|
670
|
+
* - **Default naming**: the parent name is prepended so the full path is encoded
|
|
671
|
+
* (e.g. `OrderParams` when parent is `Order`).
|
|
672
|
+
*/
|
|
673
|
+
function resolveChildName(parentName: string | undefined, propName: string): string | undefined {
|
|
674
|
+
if (isLegacyNaming) {
|
|
675
|
+
return pascalCase(propName)
|
|
676
|
+
}
|
|
677
|
+
return parentName ? pascalCase([parentName, propName].join(' ')) : undefined
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Derives the final name for an enum property schema node.
|
|
682
|
+
*
|
|
683
|
+
* The raw name always includes the enum suffix (e.g. `StatusEnum`).
|
|
684
|
+
* In legacy mode an additional deduplication step appends a numeric suffix
|
|
685
|
+
* when the same name has already been used (e.g. `ParamsStatusEnum2`).
|
|
686
|
+
*/
|
|
687
|
+
function resolveEnumPropName(parentName: string | undefined, propName: string, enumSuffix: string): string {
|
|
688
|
+
const raw = pascalCase([parentName, propName, enumSuffix].filter(Boolean).join(' '))
|
|
689
|
+
return isLegacyNaming ? getUniqueName(raw, usedEnumNames) : raw
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Given a freshly-converted property schema, returns the node with a correct
|
|
694
|
+
* `name` attached — or stripped — depending on whether the node is a named
|
|
695
|
+
* enum, a boolean const-enum (always inlined), or a regular schema.
|
|
696
|
+
*/
|
|
697
|
+
function applyEnumName(propNode: SchemaNode, parentName: string | undefined, propName: string, enumSuffix: string): SchemaNode {
|
|
698
|
+
const enumNode = narrowSchema(propNode, 'enum')
|
|
722
699
|
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
700
|
+
// Boolean-primitive enum nodes (e.g. `const: false`) are always inlined as
|
|
701
|
+
// literal types and must not receive a named identifier.
|
|
702
|
+
if (enumNode?.primitive === 'boolean') {
|
|
703
|
+
return { ...propNode, name: undefined }
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (enumNode) {
|
|
707
|
+
return { ...propNode, name: resolveEnumPropName(parentName, propName, enumSuffix) }
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return propNode
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
|
|
714
|
+
const properties: Array<PropertyNode> = schema.properties
|
|
715
|
+
? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
716
|
+
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
|
|
726
717
|
const resolvedPropSchema = propSchema as SchemaObject
|
|
727
718
|
const propNullable = isNullable(resolvedPropSchema)
|
|
728
|
-
|
|
719
|
+
|
|
720
|
+
const childName = resolveChildName(name, propName)
|
|
721
|
+
const propNode = convertSchema({ schema: resolvedPropSchema, name: childName }, options)
|
|
722
|
+
const schemaNode = applyEnumName(propNode, name, propName, mergedOptions.enumSuffix)
|
|
729
723
|
|
|
730
724
|
return createProperty({
|
|
731
725
|
name: propName,
|
|
732
726
|
schema: {
|
|
733
|
-
...
|
|
727
|
+
...schemaNode,
|
|
734
728
|
nullable: propNullable || undefined,
|
|
735
729
|
optional: !required && !propNullable ? true : undefined,
|
|
736
730
|
nullish: !required && propNullable ? true : undefined,
|
|
@@ -740,7 +734,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
740
734
|
})
|
|
741
735
|
: []
|
|
742
736
|
|
|
743
|
-
const additionalProperties =
|
|
737
|
+
const additionalProperties = schema.additionalProperties
|
|
744
738
|
let additionalPropertiesNode: SchemaNode | true | undefined
|
|
745
739
|
if (additionalProperties === true) {
|
|
746
740
|
additionalPropertiesNode = true
|
|
@@ -752,28 +746,38 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
752
746
|
additionalPropertiesNode = createSchema({ type: resolveTypeOption(mergedOptions.unknownType) })
|
|
753
747
|
}
|
|
754
748
|
|
|
755
|
-
const rawPatternProperties =
|
|
756
|
-
'patternProperties' in resolvedSchema ? (resolvedSchema as unknown as { patternProperties?: Record<string, SchemaObject> }).patternProperties : undefined
|
|
749
|
+
const rawPatternProperties = 'patternProperties' in schema ? schema.patternProperties : undefined
|
|
757
750
|
|
|
758
751
|
const patternProperties = rawPatternProperties
|
|
759
752
|
? Object.fromEntries(
|
|
760
753
|
Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [
|
|
761
754
|
pattern,
|
|
762
|
-
|
|
755
|
+
patternSchema === true || (typeof patternSchema === 'object' && Object.keys(patternSchema).length === 0)
|
|
763
756
|
? createSchema({ type: resolveTypeOption(mergedOptions.unknownType) })
|
|
764
757
|
: convertSchema({ schema: patternSchema as SchemaObject }, options),
|
|
765
758
|
]),
|
|
766
759
|
)
|
|
767
760
|
: undefined
|
|
768
761
|
|
|
769
|
-
|
|
762
|
+
const objectNode: SchemaNode = createSchema({
|
|
770
763
|
type: 'object',
|
|
771
764
|
primitive: 'object',
|
|
772
765
|
properties,
|
|
773
766
|
additionalProperties: additionalPropertiesNode,
|
|
774
767
|
patternProperties,
|
|
775
|
-
...
|
|
768
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
776
769
|
})
|
|
770
|
+
|
|
771
|
+
// When a discriminator is present, replace the discriminator property's schema
|
|
772
|
+
// with an enum of the mapping keys for a precise literal-union type.
|
|
773
|
+
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
774
|
+
const discPropName = schema.discriminator.propertyName
|
|
775
|
+
const values = Object.keys(schema.discriminator.mapping)
|
|
776
|
+
const enumName = name ? resolveEnumPropName(name, discPropName, mergedOptions.enumSuffix) : undefined
|
|
777
|
+
return applyDiscriminatorEnum({ node: objectNode, propertyName: discPropName, values, enumName })
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return objectNode
|
|
777
781
|
}
|
|
778
782
|
|
|
779
783
|
/**
|
|
@@ -783,9 +787,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
783
787
|
* after the prefix items is mapped to the rest parameter of the tuple.
|
|
784
788
|
*/
|
|
785
789
|
function convertTuple({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
const rest = rawSchema.items ? convertSchema({ schema: rawSchema.items }, options) : undefined
|
|
790
|
+
const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject }, options))
|
|
791
|
+
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) : undefined
|
|
789
792
|
|
|
790
793
|
return createSchema({
|
|
791
794
|
type: 'tuple',
|
|
@@ -794,7 +797,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
794
797
|
rest,
|
|
795
798
|
min: schema.minItems,
|
|
796
799
|
max: schema.maxItems,
|
|
797
|
-
...
|
|
800
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
798
801
|
})
|
|
799
802
|
}
|
|
800
803
|
|
|
@@ -805,12 +808,11 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
805
808
|
* `enumSuffix` is forwarded so generators can emit a named enum declaration.
|
|
806
809
|
*/
|
|
807
810
|
function convertArray({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
|
|
808
|
-
const
|
|
809
|
-
// When the
|
|
810
|
-
//
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const items = rawSchema.items ? [convertSchema({ schema: rawSchema.items, name: itemName }, options)] : []
|
|
811
|
+
const rawItems = schema.items as SchemaObject | undefined
|
|
812
|
+
// When the items schema contains an inline enum, derive a named identifier
|
|
813
|
+
// so generators can emit a standalone enum declaration.
|
|
814
|
+
const itemName = rawItems?.enum?.length && name ? resolveEnumPropName(undefined, name, mergedOptions.enumSuffix) : undefined
|
|
815
|
+
const items = rawItems ? [convertSchema({ schema: rawItems, name: itemName }, options)] : []
|
|
814
816
|
|
|
815
817
|
return createSchema({
|
|
816
818
|
type: 'array',
|
|
@@ -819,7 +821,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
819
821
|
min: schema.minItems,
|
|
820
822
|
max: schema.maxItems,
|
|
821
823
|
unique: schema.uniqueItems ?? undefined,
|
|
822
|
-
...
|
|
824
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
823
825
|
})
|
|
824
826
|
}
|
|
825
827
|
|
|
@@ -833,7 +835,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
833
835
|
min: schema.minLength,
|
|
834
836
|
max: schema.maxLength,
|
|
835
837
|
pattern: schema.pattern,
|
|
836
|
-
...
|
|
838
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
837
839
|
})
|
|
838
840
|
}
|
|
839
841
|
|
|
@@ -848,7 +850,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
848
850
|
max: schema.maximum,
|
|
849
851
|
exclusiveMinimum: typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined,
|
|
850
852
|
exclusiveMaximum: typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined,
|
|
851
|
-
...
|
|
853
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
852
854
|
})
|
|
853
855
|
}
|
|
854
856
|
|
|
@@ -859,7 +861,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
859
861
|
return createSchema({
|
|
860
862
|
type: 'boolean',
|
|
861
863
|
primitive: 'boolean',
|
|
862
|
-
...
|
|
864
|
+
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
863
865
|
})
|
|
864
866
|
}
|
|
865
867
|
|
|
@@ -894,12 +896,12 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
894
896
|
* 10. Object / array / tuple / scalar by `type`
|
|
895
897
|
* 11. Empty schema fallback (`emptySchemaType` option)
|
|
896
898
|
*/
|
|
897
|
-
function convertSchema({ schema, name }: { schema: SchemaObject; name?: string }, options?: Partial<
|
|
898
|
-
const mergedOptions:
|
|
899
|
+
function convertSchema({ schema, name }: { schema: SchemaObject; name?: string }, options?: Partial<ParserOptions>): SchemaNode {
|
|
900
|
+
const mergedOptions: ParserOptions = { ...DEFAULT_PARSER_OPTIONS, ...options }
|
|
899
901
|
// Flatten keyword-only allOf fragments (no $ref, no structural keys) into the parent
|
|
900
902
|
// schema before parsing, so simple annotation patterns don't produce needless intersections.
|
|
901
|
-
const flattenedSchema = flattenSchema(schema
|
|
902
|
-
if (flattenedSchema && flattenedSchema !==
|
|
903
|
+
const flattenedSchema = flattenSchema(schema)
|
|
904
|
+
if (flattenedSchema && flattenedSchema !== schema) {
|
|
903
905
|
return convertSchema({ schema: flattenedSchema, name }, options)
|
|
904
906
|
}
|
|
905
907
|
|
|
@@ -933,8 +935,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
933
935
|
}
|
|
934
936
|
|
|
935
937
|
// OAS 3.1: `contentMediaType: 'application/octet-stream'` on a string schema signals binary data.
|
|
936
|
-
if (schema.type === 'string' &&
|
|
937
|
-
return createSchema({ type: 'blob', primitive: 'string', ...
|
|
938
|
+
if (schema.type === 'string' && schema.contentMediaType === 'application/octet-stream') {
|
|
939
|
+
return createSchema({ type: 'blob', primitive: 'string', ...renderSchemaBase(schema, name, nullable, defaultValue) })
|
|
938
940
|
}
|
|
939
941
|
|
|
940
942
|
// OAS 3.1: `type` may be an array — e.g. `["string", "integer", "null"]`.
|
|
@@ -947,8 +949,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
947
949
|
if (nonNullTypes.length > 1) {
|
|
948
950
|
return createSchema({
|
|
949
951
|
type: 'union',
|
|
950
|
-
members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject }, options)),
|
|
951
|
-
...
|
|
952
|
+
members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject, name }, options)),
|
|
953
|
+
...renderSchemaBase(schema, name, arrayNullable, defaultValue),
|
|
952
954
|
})
|
|
953
955
|
}
|
|
954
956
|
}
|
|
@@ -983,17 +985,23 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
983
985
|
* Converts a single dereferenced OAS parameter object into a `ParameterNode`.
|
|
984
986
|
* When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
|
|
985
987
|
*/
|
|
986
|
-
function parseParameter(options:
|
|
987
|
-
const
|
|
988
|
-
|
|
988
|
+
function parseParameter(options: ParserOptions, param: Record<string, unknown>): ParameterNode {
|
|
989
|
+
const required = (param['required'] as boolean | undefined) ?? false
|
|
990
|
+
|
|
991
|
+
const schema: SchemaNode =
|
|
992
|
+
param['schema'] && !isReference(param['schema'])
|
|
989
993
|
? convertSchema({ schema: param['schema'] as SchemaObject }, options)
|
|
990
994
|
: createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
991
995
|
|
|
992
996
|
return createParameter({
|
|
993
997
|
name: param['name'] as string,
|
|
994
998
|
in: param['in'] as ParameterLocation,
|
|
995
|
-
schema
|
|
996
|
-
|
|
999
|
+
schema: {
|
|
1000
|
+
...schema,
|
|
1001
|
+
description: (param['description'] as string | undefined) ?? schema.description,
|
|
1002
|
+
optional: !required || !!schema.optional ? true : undefined,
|
|
1003
|
+
},
|
|
1004
|
+
required,
|
|
997
1005
|
})
|
|
998
1006
|
}
|
|
999
1007
|
|
|
@@ -1001,12 +1009,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1001
1009
|
* Converts an OAS `Operation` into an `OperationNode`, resolving parameters,
|
|
1002
1010
|
* request body, and all response codes into their AST node equivalents.
|
|
1003
1011
|
*/
|
|
1004
|
-
function parseOperation(options:
|
|
1005
|
-
const parameters: Array<ParameterNode> =
|
|
1006
|
-
const dereferenced = oas.dereferenceWithRef(param) as unknown as Record<string, unknown>
|
|
1007
|
-
|
|
1008
|
-
return parseParameter(options, dereferenced)
|
|
1009
|
-
})
|
|
1012
|
+
function parseOperation(options: ParserOptions, oas: Oas, operation: Operation): OperationNode {
|
|
1013
|
+
const parameters: Array<ParameterNode> = oas.getParameters(operation).map((param) => parseParameter(options, param as unknown as Record<string, unknown>))
|
|
1010
1014
|
|
|
1011
1015
|
const requestBodySchema = oas.getRequestSchema(operation)
|
|
1012
1016
|
const requestBody = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : undefined
|
|
@@ -1015,7 +1019,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1015
1019
|
const responseObj = operation.getResponseByStatusCode(statusCode)
|
|
1016
1020
|
const responseSchema = oas.getResponseSchema(operation, statusCode)
|
|
1017
1021
|
|
|
1018
|
-
const schema =
|
|
1022
|
+
const schema =
|
|
1023
|
+
responseSchema && Object.keys(responseSchema).length > 0
|
|
1024
|
+
? convertSchema({ schema: responseSchema }, options)
|
|
1025
|
+
: createSchema({ type: resolveTypeOption(options.emptySchemaType) })
|
|
1019
1026
|
|
|
1020
1027
|
const description = typeof responseObj === 'object' && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : undefined
|
|
1021
1028
|
|
|
@@ -1037,7 +1044,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1037
1044
|
return createOperation({
|
|
1038
1045
|
operationId: operation.getOperationId(),
|
|
1039
1046
|
method: operation.method.toUpperCase() as HttpMethod,
|
|
1040
|
-
path: operation.path,
|
|
1047
|
+
path: new URLPath(operation.path).URL,
|
|
1041
1048
|
tags: operation.getTags().map((tag) => tag.name),
|
|
1042
1049
|
summary: operation.getSummary() || undefined,
|
|
1043
1050
|
description: operation.getDescription() || undefined,
|
|
@@ -1052,8 +1059,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1052
1059
|
* Converts an OpenAPI/Swagger spec (wrapped in a Kubb `Oas` instance) into
|
|
1053
1060
|
* a `RootNode` — the top-level node of the `@kubb/ast` tree.
|
|
1054
1061
|
*/
|
|
1055
|
-
function parse<TOptions extends Partial<
|
|
1056
|
-
const mergedOptions:
|
|
1062
|
+
function parse<TOptions extends Partial<ParserOptions> = object>(options?: TOptions): RootNode {
|
|
1063
|
+
const mergedOptions: ParserOptions = { ...DEFAULT_PARSER_OPTIONS, ...options }
|
|
1057
1064
|
|
|
1058
1065
|
const schemas: Array<SchemaNode> = Object.entries(schemaObjects).map(([name, schemaObject]) =>
|
|
1059
1066
|
convertSchema({ schema: schemaObject as SchemaObject, name }, mergedOptions),
|
|
@@ -1101,36 +1108,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1101
1108
|
}) as SchemaNode
|
|
1102
1109
|
}
|
|
1103
1110
|
|
|
1104
|
-
/**
|
|
1105
|
-
* Collects all `KubbFile.Import` descriptors needed by a `SchemaNode` tree.
|
|
1106
|
-
*
|
|
1107
|
-
* Walks the tree looking for `ref` nodes, verifies each `$ref` is resolvable in the spec,
|
|
1108
|
-
* applies collision-resolved names from `nameMapping`, and calls `resolve` to obtain the
|
|
1109
|
-
* import path and name. Returns an empty array for refs that cannot be resolved.
|
|
1110
|
-
*/
|
|
1111
|
-
function getImports(node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string } | undefined): Array<KubbFile.Import> {
|
|
1112
|
-
return collect<KubbFile.Import>(node, {
|
|
1113
|
-
schema(schemaNode): KubbFile.Import | undefined {
|
|
1114
|
-
if (schemaNode.type !== 'ref' || !schemaNode.ref) return
|
|
1115
|
-
// Use the OAS instance to verify this $ref is importable (exists in the spec).
|
|
1116
|
-
if (!oas.get(schemaNode.ref)) return
|
|
1117
|
-
|
|
1118
|
-
const rawName = extractRefName(schemaNode.ref)
|
|
1119
|
-
|
|
1120
|
-
// Apply collision-resolved name if available.
|
|
1121
|
-
const schemaName = nameMapping.get(rawName) ?? rawName
|
|
1122
|
-
const result = resolve(schemaName)
|
|
1123
|
-
if (!result) return
|
|
1124
|
-
|
|
1125
|
-
return { name: [result.name], path: result.path }
|
|
1126
|
-
},
|
|
1127
|
-
})
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
1111
|
return {
|
|
1131
|
-
parse
|
|
1112
|
+
parse,
|
|
1132
1113
|
convertSchema,
|
|
1133
1114
|
resolveRefs,
|
|
1134
|
-
|
|
1115
|
+
nameMapping,
|
|
1135
1116
|
} as OasParser
|
|
1136
1117
|
}
|