@kubb/adapter-oas 5.0.0-alpha.15 → 5.0.0-alpha.16
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 +157 -343
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +151 -341
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +1 -1
- package/src/constants.ts +0 -49
- package/src/discriminator.ts +25 -0
- package/src/infer.ts +85 -0
- package/src/naming.ts +25 -0
- package/src/oas/types.ts +10 -4
- package/src/parser.ts +122 -323
- package/src/refResolver.ts +65 -0
- package/src/utils.ts +0 -168
package/src/parser.ts
CHANGED
|
@@ -1,120 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { URLPath } from '@internals/utils'
|
|
2
|
+
import {
|
|
3
|
+
applyDiscriminatorEnum,
|
|
4
|
+
createOperation,
|
|
5
|
+
createParameter,
|
|
6
|
+
createProperty,
|
|
7
|
+
createResponse,
|
|
8
|
+
createRoot,
|
|
9
|
+
createSchema,
|
|
10
|
+
extractRefName,
|
|
11
|
+
mediaTypes,
|
|
12
|
+
mergeAdjacentAnonymousObjects,
|
|
13
|
+
narrowSchema,
|
|
14
|
+
schemaTypes,
|
|
15
|
+
simplifyUnionMembers,
|
|
16
|
+
} from '@kubb/ast'
|
|
3
17
|
import type {
|
|
4
|
-
|
|
5
|
-
DateSchemaNode,
|
|
6
|
-
DatetimeSchemaNode,
|
|
7
|
-
EnumSchemaNode,
|
|
18
|
+
DistributiveOmit,
|
|
8
19
|
HttpMethod,
|
|
9
|
-
IntersectionSchemaNode,
|
|
10
20
|
MediaType,
|
|
11
|
-
NumberSchemaNode,
|
|
12
|
-
ObjectSchemaNode,
|
|
13
21
|
OperationNode,
|
|
14
22
|
ParameterLocation,
|
|
15
23
|
ParameterNode,
|
|
16
24
|
PrimitiveSchemaType,
|
|
17
25
|
PropertyNode,
|
|
18
|
-
RefSchemaNode,
|
|
19
26
|
ResponseNode,
|
|
20
27
|
RootNode,
|
|
21
|
-
ScalarSchemaNode,
|
|
22
28
|
ScalarSchemaType,
|
|
23
29
|
SchemaNode,
|
|
24
30
|
SchemaType,
|
|
25
31
|
StatusCode,
|
|
26
|
-
StringSchemaNode,
|
|
27
|
-
TimeSchemaNode,
|
|
28
|
-
UnionSchemaNode,
|
|
29
32
|
} from '@kubb/ast/types'
|
|
30
|
-
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, formatMap
|
|
33
|
+
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, formatMap } from './constants.ts'
|
|
34
|
+
import { createDiscriminantNode, resolveDiscriminatorValue } from './discriminator.ts'
|
|
35
|
+
import type { InferSchemaNode } from './infer.ts'
|
|
36
|
+
import { applyEnumName, resolveChildName, resolveEnumPropName } from './naming.ts'
|
|
31
37
|
import type { Oas } from './oas/Oas.ts'
|
|
32
38
|
import type { contentType, Operation, ReferenceObject, SchemaObject } from './oas/types.ts'
|
|
33
39
|
import { flattenSchema, isDiscriminator, isNullable, isReference } from './oas/utils.ts'
|
|
40
|
+
import { resolveRefs as resolveRefsNode } from './refResolver.ts'
|
|
34
41
|
import type { ParserOptions } from './types.ts'
|
|
35
|
-
import { applyDiscriminatorEnum, extractRefName, mergeAdjacentAnonymousObjects, simplifyUnionMembers } from './utils.ts'
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Distributive `Omit` — correctly distributes over union types so that
|
|
39
|
-
* `Omit<A | B, 'kind'>` produces `Omit<A, 'kind'> | Omit<B, 'kind'>`
|
|
40
|
-
* rather than `Omit<A | B, 'kind'>`.
|
|
41
|
-
*/
|
|
42
|
-
type DistributiveOmit<TValue, TKey extends PropertyKey> = TValue extends unknown ? Omit<TValue, TKey> : never
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Maps each `dateType` option value to the AST node produced by `format: 'date-time'`.
|
|
46
|
-
*/
|
|
47
|
-
type DateTimeNodeByDateType = {
|
|
48
|
-
date: DateSchemaNode
|
|
49
|
-
string: DatetimeSchemaNode
|
|
50
|
-
stringOffset: DatetimeSchemaNode
|
|
51
|
-
stringLocal: DatetimeSchemaNode
|
|
52
|
-
false: StringSchemaNode
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Resolves the AST node produced by `format: 'date-time'` based on the `dateType` option.
|
|
57
|
-
*/
|
|
58
|
-
type ResolveDateTimeNode<TDateType extends ParserOptions['dateType']> = DateTimeNodeByDateType[TDateType extends keyof DateTimeNodeByDateType
|
|
59
|
-
? TDateType
|
|
60
|
-
: 'string']
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Single source of truth: ordered list of `[shape, SchemaNode]` pairs.
|
|
64
|
-
* `InferSchemaNode` walks this tuple in order and returns the node type of the first matching entry.
|
|
65
|
-
* Parameterized over `TDateType` so `format: 'date-time'` resolves to the correct node based on the option.
|
|
66
|
-
*/
|
|
67
|
-
type SchemaNodeMap<TDateType extends ParserOptions['dateType'] = 'string'> = [
|
|
68
|
-
[{ $ref: string }, RefSchemaNode],
|
|
69
|
-
// allOf with sibling `properties` always produces an intersection (shared props are appended as a member).
|
|
70
|
-
[{ allOf: ReadonlyArray<unknown>; properties: object }, IntersectionSchemaNode],
|
|
71
|
-
// allOf with 2+ members always produces an intersection.
|
|
72
|
-
[{ allOf: readonly [unknown, unknown, ...unknown[]] }, IntersectionSchemaNode],
|
|
73
|
-
// Single-member allOf without sibling `properties` flattens to the member type.
|
|
74
|
-
[{ allOf: ReadonlyArray<unknown> }, SchemaNode],
|
|
75
|
-
[{ oneOf: ReadonlyArray<unknown> }, UnionSchemaNode],
|
|
76
|
-
[{ anyOf: ReadonlyArray<unknown> }, UnionSchemaNode],
|
|
77
|
-
[{ const: null }, ScalarSchemaNode],
|
|
78
|
-
[{ const: string | number | boolean }, EnumSchemaNode],
|
|
79
|
-
// OAS 3.1 multi-type array: `{ type: ['string', 'integer'] }` → union node.
|
|
80
|
-
[{ type: ReadonlyArray<string> }, UnionSchemaNode],
|
|
81
|
-
// `{ type: 'array', enum }` is normalized at runtime: enum moves into items → array node.
|
|
82
|
-
[{ type: 'array'; enum: ReadonlyArray<unknown> }, ArraySchemaNode],
|
|
83
|
-
[{ enum: ReadonlyArray<unknown> }, EnumSchemaNode],
|
|
84
|
-
[{ type: 'object' }, ObjectSchemaNode],
|
|
85
|
-
[{ additionalProperties: boolean | {} }, ObjectSchemaNode],
|
|
86
|
-
[{ type: 'array' }, ArraySchemaNode],
|
|
87
|
-
[{ items: object }, ArraySchemaNode],
|
|
88
|
-
[{ prefixItems: ReadonlyArray<unknown> }, ArraySchemaNode],
|
|
89
|
-
// Format entries with explicit type — placed before generic type entries so format wins.
|
|
90
|
-
[{ type: string; format: 'date-time' }, ResolveDateTimeNode<TDateType>],
|
|
91
|
-
[{ type: string; format: 'date' }, DateSchemaNode],
|
|
92
|
-
[{ type: string; format: 'time' }, TimeSchemaNode],
|
|
93
|
-
[{ format: 'date-time' }, ResolveDateTimeNode<TDateType>],
|
|
94
|
-
[{ format: 'date' }, DateSchemaNode],
|
|
95
|
-
[{ format: 'time' }, TimeSchemaNode],
|
|
96
|
-
[{ type: 'string' }, StringSchemaNode],
|
|
97
|
-
[{ type: 'number' }, NumberSchemaNode],
|
|
98
|
-
[{ type: 'integer' }, NumberSchemaNode],
|
|
99
|
-
[{ type: 'bigint' }, NumberSchemaNode],
|
|
100
|
-
[{ type: string }, ScalarSchemaNode],
|
|
101
|
-
// Inferred scalar types from constraints when no explicit type is present.
|
|
102
|
-
[{ minLength: number }, StringSchemaNode],
|
|
103
|
-
[{ maxLength: number }, StringSchemaNode],
|
|
104
|
-
[{ pattern: string }, StringSchemaNode],
|
|
105
|
-
[{ minimum: number }, NumberSchemaNode],
|
|
106
|
-
[{ maximum: number }, NumberSchemaNode],
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
export type InferSchemaNode<
|
|
110
|
-
TSchema extends SchemaObject,
|
|
111
|
-
TDateType extends ParserOptions['dateType'] = 'string',
|
|
112
|
-
TEntries extends ReadonlyArray<[object, SchemaNode]> = SchemaNodeMap<TDateType>,
|
|
113
|
-
> = TEntries extends [infer TEntry extends [object, SchemaNode], ...infer TRest extends ReadonlyArray<[object, SchemaNode]>]
|
|
114
|
-
? TSchema extends TEntry[0]
|
|
115
|
-
? TEntry[1]
|
|
116
|
-
: InferSchemaNode<TSchema, TDateType, TRest>
|
|
117
|
-
: SchemaNode
|
|
118
42
|
|
|
119
43
|
/**
|
|
120
44
|
* Construction-time options for `createOasParser`.
|
|
@@ -148,7 +72,7 @@ function getPrimitiveType(type: string | undefined): PrimitiveSchemaType {
|
|
|
148
72
|
* Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
|
|
149
73
|
*/
|
|
150
74
|
function toMediaType(contentType: string): MediaType | undefined {
|
|
151
|
-
return
|
|
75
|
+
return Object.values(mediaTypes).includes(contentType as MediaType) ? (contentType as MediaType) : undefined
|
|
152
76
|
}
|
|
153
77
|
|
|
154
78
|
/**
|
|
@@ -164,8 +88,8 @@ type SchemaContext = {
|
|
|
164
88
|
* Normalized single type string (first element when OAS 3.1 multi-type array).
|
|
165
89
|
*/
|
|
166
90
|
type: string | undefined
|
|
167
|
-
|
|
168
|
-
|
|
91
|
+
rawOptions: Partial<ParserOptions> | undefined
|
|
92
|
+
options: ParserOptions
|
|
169
93
|
}
|
|
170
94
|
|
|
171
95
|
/**
|
|
@@ -225,14 +149,25 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
225
149
|
// e.g., { '#/components/schemas/Order': 'OrderSchema', '#/components/responses/Product': 'ProductResponse' }
|
|
226
150
|
const { schemas: schemaObjects, nameMapping } = oas.getSchemas({ contentType })
|
|
227
151
|
|
|
152
|
+
const TYPE_OPTION_MAP: Record<'any' | 'unknown' | 'void', ScalarSchemaType> = {
|
|
153
|
+
any: schemaTypes.any,
|
|
154
|
+
unknown: schemaTypes.unknown,
|
|
155
|
+
void: schemaTypes.void,
|
|
156
|
+
}
|
|
157
|
+
|
|
228
158
|
/**
|
|
229
159
|
* Maps an `'any' | 'unknown' | 'void'` option string to the corresponding `SchemaType` constant.
|
|
230
160
|
* Used for both `unknownType` (unannotated schemas) and `emptySchemaType` (empty `{}` schemas).
|
|
231
161
|
*/
|
|
232
162
|
function resolveTypeOption(value: 'any' | 'unknown' | 'void'): ScalarSchemaType {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
163
|
+
return TYPE_OPTION_MAP[value]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeArrayEnum(schema: SchemaObject): SchemaObject {
|
|
167
|
+
const isItemsObject = typeof schema.items === 'object' && !Array.isArray(schema.items)
|
|
168
|
+
const normalizedItems: SchemaObject = { ...(isItemsObject ? (schema.items as SchemaObject) : {}), enum: schema.enum }
|
|
169
|
+
const { enum: _enum, ...schemaWithoutEnum } = schema
|
|
170
|
+
return { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject
|
|
236
171
|
}
|
|
237
172
|
|
|
238
173
|
/**
|
|
@@ -328,7 +263,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
328
263
|
* Circular references through discriminator parents are detected and skipped to prevent
|
|
329
264
|
* infinite recursion during code generation.
|
|
330
265
|
*/
|
|
331
|
-
function convertAllOf({ schema, name, nullable, defaultValue,
|
|
266
|
+
function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): SchemaNode {
|
|
332
267
|
if (
|
|
333
268
|
schema.allOf!.length === 1 &&
|
|
334
269
|
!schema.properties &&
|
|
@@ -336,7 +271,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
336
271
|
schema.additionalProperties === undefined
|
|
337
272
|
) {
|
|
338
273
|
const [memberSchema] = schema.allOf as Array<SchemaObject | ReferenceObject>
|
|
339
|
-
const memberNode = convertSchema({ schema: memberSchema! as SchemaObject },
|
|
274
|
+
const memberNode = convertSchema({ schema: memberSchema! as SchemaObject }, rawOptions)
|
|
340
275
|
const { kind: _kind, ...memberNodeProps } = memberNode
|
|
341
276
|
const mergedNullable = nullable || memberNode.nullable || undefined
|
|
342
277
|
const mergedDefault = schema.default === null && mergedNullable ? undefined : (schema.default ?? memberNode.default)
|
|
@@ -371,7 +306,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
371
306
|
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
372
307
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
373
308
|
if (inOneOf || inMapping) {
|
|
374
|
-
const discriminatorValue =
|
|
309
|
+
const discriminatorValue = resolveDiscriminatorValue(deref.discriminator.mapping, childRef)
|
|
375
310
|
if (discriminatorValue) {
|
|
376
311
|
filteredDiscriminantValues.push({ propertyName: deref.discriminator.propertyName, value: discriminatorValue })
|
|
377
312
|
}
|
|
@@ -379,7 +314,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
379
314
|
}
|
|
380
315
|
return true
|
|
381
316
|
})
|
|
382
|
-
.map((s) => convertSchema({ schema: s as SchemaObject },
|
|
317
|
+
.map((s) => convertSchema({ schema: s as SchemaObject }, rawOptions))
|
|
383
318
|
|
|
384
319
|
// Track where allOf-derived members end so each portion can be merged independently.
|
|
385
320
|
const syntheticStart = allOfMembers.length
|
|
@@ -400,7 +335,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
400
335
|
for (const key of missingRequired) {
|
|
401
336
|
for (const resolved of resolvedMembers) {
|
|
402
337
|
if (resolved.properties?.[key]) {
|
|
403
|
-
allOfMembers.push(convertSchema({ schema: { properties: { [key]: resolved.properties[key] }, required: [key] } as SchemaObject },
|
|
338
|
+
allOfMembers.push(convertSchema({ schema: { properties: { [key]: resolved.properties[key] }, required: [key] } as SchemaObject }, rawOptions))
|
|
404
339
|
break
|
|
405
340
|
}
|
|
406
341
|
}
|
|
@@ -410,29 +345,13 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
410
345
|
|
|
411
346
|
if (schema.properties) {
|
|
412
347
|
const { allOf: _allOf, ...schemaWithoutAllOf } = schema
|
|
413
|
-
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf },
|
|
348
|
+
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf }, rawOptions))
|
|
414
349
|
}
|
|
415
350
|
|
|
416
351
|
// Inject a synthetic single-property object for each discriminant value collected from
|
|
417
352
|
// filtered discriminator parents so that child schemas carry the narrowed literal type.
|
|
418
353
|
for (const { propertyName, value } of filteredDiscriminantValues) {
|
|
419
|
-
allOfMembers.push(
|
|
420
|
-
createSchema({
|
|
421
|
-
type: 'object',
|
|
422
|
-
primitive: 'object',
|
|
423
|
-
properties: [
|
|
424
|
-
createProperty({
|
|
425
|
-
name: propertyName,
|
|
426
|
-
schema: createSchema({
|
|
427
|
-
type: 'enum',
|
|
428
|
-
primitive: 'string',
|
|
429
|
-
enumValues: [value],
|
|
430
|
-
}),
|
|
431
|
-
required: true,
|
|
432
|
-
}),
|
|
433
|
-
],
|
|
434
|
-
}),
|
|
435
|
-
)
|
|
354
|
+
allOfMembers.push(createDiscriminantNode(propertyName, value))
|
|
436
355
|
}
|
|
437
356
|
|
|
438
357
|
// Merge consecutive anonymous object members within the synthetic portion — see `mergeAdjacentAnonymousObjects`.
|
|
@@ -451,81 +370,56 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
451
370
|
* individually intersected with the shared properties node to match the OAS pattern of
|
|
452
371
|
* adding common fields next to a discriminated union.
|
|
453
372
|
*/
|
|
454
|
-
function convertUnion({ schema, name, nullable, defaultValue,
|
|
373
|
+
function convertUnion({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): SchemaNode {
|
|
455
374
|
const unionMembers = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])]
|
|
456
375
|
const unionBase = {
|
|
457
376
|
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
458
377
|
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
459
378
|
}
|
|
379
|
+
const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
|
|
380
|
+
const sharedPropertiesNode = schema.properties
|
|
381
|
+
? (() => {
|
|
382
|
+
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
383
|
+
// Strip discriminator so convertObject won't re-apply the full mapping enum.
|
|
384
|
+
const memberBaseSchema: SchemaObject = discriminator
|
|
385
|
+
? (Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== 'discriminator')) as SchemaObject)
|
|
386
|
+
: schemaWithoutUnion
|
|
387
|
+
// Convert shared properties once to avoid duplicate enum naming
|
|
388
|
+
// (e.g. StatusEnum appearing twice and getting a numeric suffix).
|
|
389
|
+
return convertSchema({ schema: memberBaseSchema, name }, rawOptions)
|
|
390
|
+
})()
|
|
391
|
+
: undefined
|
|
460
392
|
|
|
461
|
-
if (
|
|
462
|
-
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
463
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
|
|
464
|
-
|
|
465
|
-
// Strip discriminator so convertObject won't re-apply the full mapping enum.
|
|
466
|
-
const memberBaseSchema: SchemaObject = discriminator
|
|
467
|
-
? (Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== 'discriminator')) as SchemaObject)
|
|
468
|
-
: schemaWithoutUnion
|
|
469
|
-
|
|
470
|
-
// Convert shared properties once to avoid duplicate enum naming
|
|
471
|
-
// (e.g. StatusEnum appearing twice and getting a numeric suffix).
|
|
472
|
-
const sharedPropertiesNode = convertSchema({ schema: memberBaseSchema, name }, options)
|
|
473
|
-
|
|
393
|
+
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
474
394
|
return createSchema({
|
|
475
395
|
type: 'union',
|
|
476
396
|
...unionBase,
|
|
477
397
|
members: unionMembers.map((s) => {
|
|
478
398
|
const ref = isReference(s) ? s.$ref : undefined
|
|
479
|
-
const discriminatorValue = discriminator?.mapping
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
399
|
+
const discriminatorValue = resolveDiscriminatorValue(discriminator?.mapping, ref)
|
|
400
|
+
const memberNode = convertSchema({ schema: s as SchemaObject }, rawOptions)
|
|
401
|
+
|
|
402
|
+
if (sharedPropertiesNode) {
|
|
403
|
+
const narrowedProperties =
|
|
404
|
+
discriminatorValue && discriminator
|
|
405
|
+
? applyDiscriminatorEnum({
|
|
406
|
+
node: sharedPropertiesNode,
|
|
407
|
+
propertyName: discriminator.propertyName,
|
|
408
|
+
values: [discriminatorValue],
|
|
409
|
+
})
|
|
410
|
+
: sharedPropertiesNode
|
|
411
|
+
|
|
412
|
+
return createSchema({
|
|
413
|
+
type: 'intersection',
|
|
414
|
+
members: [memberNode, narrowedProperties],
|
|
415
|
+
})
|
|
485
416
|
}
|
|
486
417
|
|
|
487
|
-
return
|
|
488
|
-
type: 'intersection',
|
|
489
|
-
members: [convertSchema({ schema: s as SchemaObject }, options), propertiesNode],
|
|
490
|
-
})
|
|
491
|
-
}),
|
|
492
|
-
})
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// When a discriminator with mapping is present but there are no sibling properties,
|
|
496
|
-
// embed the narrowed discriminant value into each member to produce precise literal
|
|
497
|
-
// intersection types (e.g. `Cat & { type: 'cat' }`) instead of plain `Cat | Dog`.
|
|
498
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
|
|
499
|
-
if (discriminator?.mapping) {
|
|
500
|
-
return createSchema({
|
|
501
|
-
type: 'union',
|
|
502
|
-
...unionBase,
|
|
503
|
-
members: unionMembers.map((s) => {
|
|
504
|
-
const ref = isReference(s) ? s.$ref : undefined
|
|
505
|
-
const discriminatorValue = ref ? Object.entries(discriminator.mapping!).find(([, v]) => v === ref)?.[0] : undefined
|
|
506
|
-
const memberNode = convertSchema({ schema: s as SchemaObject }, options)
|
|
507
|
-
|
|
508
|
-
if (!discriminatorValue) return memberNode
|
|
509
|
-
|
|
510
|
-
const discriminantNode = createSchema({
|
|
511
|
-
type: 'object',
|
|
512
|
-
primitive: 'object',
|
|
513
|
-
properties: [
|
|
514
|
-
createProperty({
|
|
515
|
-
name: discriminator.propertyName,
|
|
516
|
-
schema: createSchema({
|
|
517
|
-
type: 'enum',
|
|
518
|
-
primitive: 'string',
|
|
519
|
-
enumValues: [discriminatorValue],
|
|
520
|
-
}),
|
|
521
|
-
required: true,
|
|
522
|
-
}),
|
|
523
|
-
],
|
|
524
|
-
})
|
|
418
|
+
if (!discriminatorValue || !discriminator) return memberNode
|
|
525
419
|
|
|
526
420
|
return createSchema({
|
|
527
421
|
type: 'intersection',
|
|
528
|
-
members: [memberNode,
|
|
422
|
+
members: [memberNode, createDiscriminantNode(discriminator.propertyName, discriminatorValue)],
|
|
529
423
|
})
|
|
530
424
|
}),
|
|
531
425
|
})
|
|
@@ -534,7 +428,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
534
428
|
return createSchema({
|
|
535
429
|
type: 'union',
|
|
536
430
|
...unionBase,
|
|
537
|
-
members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s as SchemaObject },
|
|
431
|
+
members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, rawOptions))),
|
|
538
432
|
})
|
|
539
433
|
}
|
|
540
434
|
|
|
@@ -573,13 +467,13 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
573
467
|
* Returns `undefined` when the format should fall through to string handling
|
|
574
468
|
* (i.e. `format: 'date-time'` with `dateType: false`).
|
|
575
469
|
*/
|
|
576
|
-
function convertFormat({ schema, name, nullable, defaultValue,
|
|
470
|
+
function convertFormat({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode | undefined {
|
|
577
471
|
const base = renderSchemaBase(schema, name, nullable, defaultValue)
|
|
578
472
|
|
|
579
473
|
// int64 is option-dependent so it can't live in the static formatMap.
|
|
580
474
|
if (schema.format === 'int64') {
|
|
581
475
|
return createSchema({
|
|
582
|
-
type:
|
|
476
|
+
type: options.integerType === 'bigint' ? 'bigint' : 'integer',
|
|
583
477
|
primitive: 'integer',
|
|
584
478
|
...base,
|
|
585
479
|
min: schema.minimum,
|
|
@@ -591,7 +485,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
591
485
|
|
|
592
486
|
// date-time / date / time are option-dependent and can't live in the static formatMap.
|
|
593
487
|
if (schema.format === 'date-time' || schema.format === 'date' || schema.format === 'time') {
|
|
594
|
-
const dateType = getDateType(
|
|
488
|
+
const dateType = getDateType(options, schema.format)
|
|
595
489
|
if (!dateType) return undefined // dateType: false → fall through to string
|
|
596
490
|
|
|
597
491
|
if (dateType.type === 'datetime') {
|
|
@@ -625,13 +519,10 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
625
519
|
* - Numeric and boolean enums require a const-map representation because most generators cannot
|
|
626
520
|
* use string-enum syntax for non-string values.
|
|
627
521
|
*/
|
|
628
|
-
function convertEnum({ schema, name, nullable, type,
|
|
522
|
+
function convertEnum({ schema, name, nullable, type, rawOptions }: SchemaContext): SchemaNode {
|
|
629
523
|
// Malformed schema: `{ type: 'array', enum: [...] }` — normalize by moving the enum into items.
|
|
630
524
|
if (type === 'array') {
|
|
631
|
-
|
|
632
|
-
const normalizedItems: SchemaObject = { ...(isItemsObject ? (schema.items as SchemaObject) : {}), enum: schema.enum }
|
|
633
|
-
const { enum: _enum, ...schemaWithoutEnum } = schema
|
|
634
|
-
return convertSchema({ schema: { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject, name }, options)
|
|
525
|
+
return convertSchema({ schema: normalizeArrayEnum(schema), name }, rawOptions)
|
|
635
526
|
}
|
|
636
527
|
|
|
637
528
|
// `null` in enum values is the OAS 3.0 convention for a nullable enum.
|
|
@@ -657,53 +548,26 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
657
548
|
|
|
658
549
|
// x-enumNames / x-enum-varnames: named variants with explicit labels take priority.
|
|
659
550
|
const extensionKey = enumExtensionKeys.find((key) => key in schema)
|
|
660
|
-
if (extensionKey) {
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
: ('string' as const)
|
|
551
|
+
if (extensionKey || enumPrimitive === 'number' || enumPrimitive === 'integer' || enumPrimitive === 'boolean') {
|
|
552
|
+
const enumType = (enumPrimitive === 'number' || enumPrimitive === 'integer' ? 'number' : enumPrimitive === 'boolean' ? 'boolean' : 'string') as
|
|
553
|
+
| 'number'
|
|
554
|
+
| 'boolean'
|
|
555
|
+
| 'string'
|
|
556
|
+
const sourceValues = extensionKey
|
|
557
|
+
? [...new Set((schema as Record<string, unknown>)[extensionKey] as Array<string | number>)]
|
|
558
|
+
: [...new Set(filteredValues)]
|
|
669
559
|
|
|
670
560
|
return createSchema({
|
|
671
561
|
...enumBase,
|
|
672
562
|
enumType,
|
|
673
|
-
namedEnumValues:
|
|
563
|
+
namedEnumValues: sourceValues.map((label, index) => ({
|
|
674
564
|
name: String(label),
|
|
675
|
-
value: filteredValues[index] ?? label,
|
|
565
|
+
value: extensionKey ? (filteredValues[index] ?? label) : label,
|
|
676
566
|
format: enumType,
|
|
677
567
|
})),
|
|
678
568
|
})
|
|
679
569
|
}
|
|
680
570
|
|
|
681
|
-
// Number / integer enum — must use a const map since most generators can't use string-enum for numbers.
|
|
682
|
-
if (type === 'number' || type === 'integer') {
|
|
683
|
-
return createSchema({
|
|
684
|
-
...enumBase,
|
|
685
|
-
enumType: 'number' as const,
|
|
686
|
-
namedEnumValues: [...new Set(filteredValues)].map((value) => ({
|
|
687
|
-
name: String(value),
|
|
688
|
-
value: value as number,
|
|
689
|
-
format: 'number' as const,
|
|
690
|
-
})),
|
|
691
|
-
})
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Boolean enum — same const-map approach as numeric.
|
|
695
|
-
if (type === 'boolean') {
|
|
696
|
-
return createSchema({
|
|
697
|
-
...enumBase,
|
|
698
|
-
enumType: 'boolean' as const,
|
|
699
|
-
namedEnumValues: [...new Set(filteredValues)].map((value) => ({
|
|
700
|
-
name: String(value),
|
|
701
|
-
value: value as boolean,
|
|
702
|
-
format: 'boolean' as const,
|
|
703
|
-
})),
|
|
704
|
-
})
|
|
705
|
-
}
|
|
706
|
-
|
|
707
571
|
// Plain string enum (default path).
|
|
708
572
|
return createSchema({
|
|
709
573
|
...enumBase,
|
|
@@ -723,48 +587,8 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
723
587
|
* - not required + not nullable → `optional: true`
|
|
724
588
|
* - not required + nullable → `nullish: true`
|
|
725
589
|
*/
|
|
726
|
-
/**
|
|
727
|
-
* Builds the propagation name for a child property during recursive schema conversion.
|
|
728
|
-
*
|
|
729
|
-
* The parent name is prepended so the full path is encoded
|
|
730
|
-
* (e.g. `OrderParams` when parent is `Order`).
|
|
731
|
-
*/
|
|
732
|
-
function resolveChildName(parentName: string | undefined, propName: string): string | undefined {
|
|
733
|
-
return parentName ? pascalCase([parentName, propName].join(' ')) : undefined
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Derives the final name for an enum property schema node.
|
|
738
|
-
*
|
|
739
|
-
* The resulting name always includes the enum suffix and full parent path context
|
|
740
|
-
* (e.g. `OrderParamsStatusEnum`).
|
|
741
|
-
*/
|
|
742
|
-
function resolveEnumPropName(parentName: string | undefined, propName: string, enumSuffix: string): string {
|
|
743
|
-
return pascalCase([parentName, propName, enumSuffix].filter(Boolean).join(' '))
|
|
744
|
-
}
|
|
745
590
|
|
|
746
|
-
|
|
747
|
-
* Given a freshly-converted property schema, returns the node with a correct
|
|
748
|
-
* `name` attached — or stripped — depending on whether the node is a named
|
|
749
|
-
* enum, a boolean const-enum (always inlined), or a regular schema.
|
|
750
|
-
*/
|
|
751
|
-
function applyEnumName(propNode: SchemaNode, parentName: string | undefined, propName: string, enumSuffix: string): SchemaNode {
|
|
752
|
-
const enumNode = narrowSchema(propNode, 'enum')
|
|
753
|
-
|
|
754
|
-
// Boolean-primitive enum nodes (e.g. `const: false`) are always inlined as
|
|
755
|
-
// literal types and must not receive a named identifier.
|
|
756
|
-
if (enumNode?.primitive === 'boolean') {
|
|
757
|
-
return { ...propNode, name: undefined }
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (enumNode) {
|
|
761
|
-
return { ...propNode, name: resolveEnumPropName(parentName, propName, enumSuffix) }
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return propNode
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
|
|
591
|
+
function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }: SchemaContext): SchemaNode {
|
|
768
592
|
const properties: Array<PropertyNode> = schema.properties
|
|
769
593
|
? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
770
594
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
|
|
@@ -772,12 +596,12 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
772
596
|
const propNullable = isNullable(resolvedPropSchema)
|
|
773
597
|
|
|
774
598
|
const childName = resolveChildName(name, propName)
|
|
775
|
-
const propNode = convertSchema({ schema: resolvedPropSchema, name: childName },
|
|
776
|
-
let schemaNode = applyEnumName(propNode, name, propName,
|
|
599
|
+
const propNode = convertSchema({ schema: resolvedPropSchema, name: childName }, rawOptions)
|
|
600
|
+
let schemaNode = applyEnumName(propNode, name, propName, options.enumSuffix)
|
|
777
601
|
|
|
778
602
|
const tupleNode = narrowSchema(schemaNode, 'tuple')
|
|
779
603
|
if (tupleNode?.items) {
|
|
780
|
-
const namedItems = tupleNode.items.map((item) => applyEnumName(item, name, propName,
|
|
604
|
+
const namedItems = tupleNode.items.map((item) => applyEnumName(item, name, propName, options.enumSuffix))
|
|
781
605
|
if (namedItems.some((item, i) => item !== tupleNode.items![i])) {
|
|
782
606
|
schemaNode = { ...tupleNode, items: namedItems }
|
|
783
607
|
}
|
|
@@ -799,11 +623,11 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
799
623
|
if (additionalProperties === true) {
|
|
800
624
|
additionalPropertiesNode = true
|
|
801
625
|
} else if (additionalProperties && Object.keys(additionalProperties).length > 0) {
|
|
802
|
-
additionalPropertiesNode = convertSchema({ schema: additionalProperties as SchemaObject },
|
|
626
|
+
additionalPropertiesNode = convertSchema({ schema: additionalProperties as SchemaObject }, rawOptions)
|
|
803
627
|
} else if (additionalProperties === false) {
|
|
804
628
|
additionalPropertiesNode = undefined
|
|
805
629
|
} else if (additionalProperties) {
|
|
806
|
-
additionalPropertiesNode = createSchema({ type: resolveTypeOption(
|
|
630
|
+
additionalPropertiesNode = createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
807
631
|
}
|
|
808
632
|
|
|
809
633
|
const rawPatternProperties = 'patternProperties' in schema ? schema.patternProperties : undefined
|
|
@@ -813,8 +637,8 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
813
637
|
Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [
|
|
814
638
|
pattern,
|
|
815
639
|
patternSchema === true || (typeof patternSchema === 'object' && Object.keys(patternSchema).length === 0)
|
|
816
|
-
? createSchema({ type: resolveTypeOption(
|
|
817
|
-
: convertSchema({ schema: patternSchema as SchemaObject },
|
|
640
|
+
? createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
641
|
+
: convertSchema({ schema: patternSchema as SchemaObject }, rawOptions),
|
|
818
642
|
]),
|
|
819
643
|
)
|
|
820
644
|
: undefined
|
|
@@ -833,7 +657,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
833
657
|
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
834
658
|
const discPropName = schema.discriminator.propertyName
|
|
835
659
|
const values = Object.keys(schema.discriminator.mapping)
|
|
836
|
-
const enumName = name ? resolveEnumPropName(name, discPropName,
|
|
660
|
+
const enumName = name ? resolveEnumPropName(name, discPropName, options.enumSuffix) : undefined
|
|
837
661
|
return applyDiscriminatorEnum({ node: objectNode, propertyName: discPropName, values, enumName })
|
|
838
662
|
}
|
|
839
663
|
|
|
@@ -848,9 +672,9 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
848
672
|
* a rest element of type `any` is emitted to match JSON Schema semantics (absent `items`
|
|
849
673
|
* means additional items are allowed).
|
|
850
674
|
*/
|
|
851
|
-
function convertTuple({ schema, name, nullable, defaultValue,
|
|
852
|
-
const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject },
|
|
853
|
-
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject },
|
|
675
|
+
function convertTuple({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): SchemaNode {
|
|
676
|
+
const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject }, rawOptions))
|
|
677
|
+
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, rawOptions) : createSchema({ type: 'any' })
|
|
854
678
|
|
|
855
679
|
return createSchema({
|
|
856
680
|
type: 'tuple',
|
|
@@ -869,12 +693,12 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
869
693
|
* When the items schema is an inline enum, a name derived from the parent array's name and
|
|
870
694
|
* `enumSuffix` is forwarded so generators can emit a named enum declaration.
|
|
871
695
|
*/
|
|
872
|
-
function convertArray({ schema, name, nullable, defaultValue,
|
|
696
|
+
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }: SchemaContext): SchemaNode {
|
|
873
697
|
const rawItems = schema.items as SchemaObject | undefined
|
|
874
698
|
// When the items schema contains an inline enum, derive a named identifier
|
|
875
699
|
// so generators can emit a standalone enum declaration.
|
|
876
|
-
const itemName = rawItems?.enum?.length && name ? resolveEnumPropName(undefined, name,
|
|
877
|
-
const items = rawItems ? [convertSchema({ schema: rawItems, name: itemName },
|
|
700
|
+
const itemName = rawItems?.enum?.length && name ? resolveEnumPropName(undefined, name, options.enumSuffix) : undefined
|
|
701
|
+
const items = rawItems ? [convertSchema({ schema: rawItems, name: itemName }, rawOptions)] : []
|
|
878
702
|
|
|
879
703
|
return createSchema({
|
|
880
704
|
type: 'array',
|
|
@@ -958,13 +782,13 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
958
782
|
* 10. Object / array / tuple / scalar by `type`
|
|
959
783
|
* 11. Empty schema fallback (`emptySchemaType` option)
|
|
960
784
|
*/
|
|
961
|
-
function convertSchema({ schema, name }: { schema: SchemaObject; name?: string },
|
|
962
|
-
const
|
|
785
|
+
function convertSchema({ schema, name }: { schema: SchemaObject; name?: string }, rawOptions?: Partial<ParserOptions>): SchemaNode {
|
|
786
|
+
const options: ParserOptions = { ...DEFAULT_PARSER_OPTIONS, ...rawOptions }
|
|
963
787
|
// Flatten keyword-only allOf fragments (no $ref, no structural keys) into the parent
|
|
964
788
|
// schema before parsing, so simple annotation patterns don't produce needless intersections.
|
|
965
789
|
const flattenedSchema = flattenSchema(schema)
|
|
966
790
|
if (flattenedSchema && flattenedSchema !== schema) {
|
|
967
|
-
return convertSchema({ schema: flattenedSchema, name },
|
|
791
|
+
return convertSchema({ schema: flattenedSchema, name }, rawOptions)
|
|
968
792
|
}
|
|
969
793
|
|
|
970
794
|
const nullable = isNullable(schema) || undefined
|
|
@@ -972,7 +796,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
972
796
|
// Normalize OAS 3.1 multi-type array to a single type string for the dispatch below.
|
|
973
797
|
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
|
|
974
798
|
|
|
975
|
-
const ctx: SchemaContext = { schema, name, nullable, defaultValue, type,
|
|
799
|
+
const ctx: SchemaContext = { schema, name, nullable, defaultValue, type, rawOptions, options }
|
|
976
800
|
|
|
977
801
|
// $ref — pointer to another definition.
|
|
978
802
|
// In OAS 3.0 siblings of $ref are technically ignored, but Kubb intentionally preserves them
|
|
@@ -1011,7 +835,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
1011
835
|
if (nonNullTypes.length > 1) {
|
|
1012
836
|
return createSchema({
|
|
1013
837
|
type: 'union',
|
|
1014
|
-
members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject, name },
|
|
838
|
+
members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject, name }, rawOptions)),
|
|
1015
839
|
...renderSchemaBase(schema, name, arrayNullable, defaultValue),
|
|
1016
840
|
})
|
|
1017
841
|
}
|
|
@@ -1039,7 +863,7 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
1039
863
|
if (type === 'boolean') return convertBoolean(ctx)
|
|
1040
864
|
if (type === 'null') return convertNull(ctx)
|
|
1041
865
|
|
|
1042
|
-
const emptyType = resolveTypeOption(
|
|
866
|
+
const emptyType = resolveTypeOption(options.emptySchemaType)
|
|
1043
867
|
return createSchema({ type: emptyType as ScalarSchemaType, name, title: schema.title, description: schema.description })
|
|
1044
868
|
}
|
|
1045
869
|
|
|
@@ -1163,36 +987,11 @@ export function createOasParser(oas: Oas, { contentType }: OasParserOptions = {}
|
|
|
1163
987
|
return createRoot({ schemas, operations })
|
|
1164
988
|
}
|
|
1165
989
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
*
|
|
1172
|
-
* Collision-resolved names (from `nameMapping`) are applied before user-supplied resolvers.
|
|
1173
|
-
*/
|
|
1174
|
-
function resolveRefs(node: SchemaNode, resolveName: (ref: string) => string | undefined, resolveEnumName?: (name: string) => string | undefined): SchemaNode {
|
|
1175
|
-
return transform(node, {
|
|
1176
|
-
schema(schemaNode) {
|
|
1177
|
-
const schemaRef = narrowSchema(schemaNode, schemaTypes.ref)
|
|
1178
|
-
|
|
1179
|
-
if (schemaRef && (schemaRef.ref || schemaRef.name)) {
|
|
1180
|
-
const rawRef = schemaRef.ref ?? schemaRef.name!
|
|
1181
|
-
const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef)
|
|
1182
|
-
if (resolved) {
|
|
1183
|
-
return { ...schemaNode, name: resolved }
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
if (schemaNode.type === 'enum' && schemaNode.name) {
|
|
1188
|
-
const resolved = (resolveEnumName ?? resolveName)(schemaNode.name)
|
|
1189
|
-
if (resolved) {
|
|
1190
|
-
return { ...schemaNode, name: resolved }
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
},
|
|
1194
|
-
}) as SchemaNode
|
|
1195
|
-
}
|
|
990
|
+
const resolveRefs = (
|
|
991
|
+
node: SchemaNode,
|
|
992
|
+
resolveName: (ref: string) => string | undefined,
|
|
993
|
+
resolveEnumName?: (name: string) => string | undefined,
|
|
994
|
+
): SchemaNode => resolveRefsNode({ node, nameMapping, resolveName, resolveEnumName })
|
|
1196
995
|
|
|
1197
996
|
return {
|
|
1198
997
|
parse,
|