@kubb/adapter-oas 5.0.0-alpha.12 → 5.0.0-alpha.14
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 +90 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +90 -30
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +2 -3
- package/src/oas/utils.ts +7 -8
- package/src/parser.ts +100 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/adapter-oas",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.14",
|
|
4
4
|
"description": "OpenAPI / Swagger adapter for Kubb — converts OAS input into a @kubb/ast RootNode.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openapi",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"openapi-types": "^12.1.3",
|
|
47
47
|
"remeda": "^2.33.6",
|
|
48
48
|
"swagger2openapi": "^7.0.8",
|
|
49
|
-
"@kubb/ast": "5.0.0-alpha.
|
|
50
|
-
"@kubb/core": "5.0.0-alpha.
|
|
49
|
+
"@kubb/ast": "5.0.0-alpha.14",
|
|
50
|
+
"@kubb/core": "5.0.0-alpha.14"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/swagger2openapi": "^7.0.4",
|
package/src/adapter.ts
CHANGED
|
@@ -87,15 +87,14 @@ export const adapterOas = createAdapter<OasAdapter>((options) => {
|
|
|
87
87
|
const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : undefined
|
|
88
88
|
|
|
89
89
|
const parser = createOasParser(oas, { contentType, collisionDetection })
|
|
90
|
+
const root = parser.parse({ dateType, integerType, unknownType, emptySchemaType, enumSuffix })
|
|
90
91
|
|
|
91
|
-
//
|
|
92
|
+
// This must happen after parse() because legacy enum remapping is finalized there.
|
|
92
93
|
nameMapping.clear()
|
|
93
94
|
for (const [key, value] of parser.nameMapping) {
|
|
94
95
|
nameMapping.set(key, value)
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
const root = parser.parse({ dateType, integerType, unknownType, emptySchemaType, enumSuffix })
|
|
98
|
-
|
|
99
98
|
return createRoot({
|
|
100
99
|
...root,
|
|
101
100
|
meta: {
|
package/src/oas/utils.ts
CHANGED
|
@@ -323,15 +323,14 @@ export function extractSchemaFromContent(content: Record<string, unknown> | unde
|
|
|
323
323
|
* Returns the PascalCase suffix appended to a component name when resolving
|
|
324
324
|
* cross-source name collisions (schemas vs. responses vs. requestBodies).
|
|
325
325
|
*/
|
|
326
|
+
const semanticSuffixes: Record<SchemaSourceMode, string> = {
|
|
327
|
+
schemas: 'Schema',
|
|
328
|
+
responses: 'Response',
|
|
329
|
+
requestBodies: 'Request',
|
|
330
|
+
}
|
|
331
|
+
|
|
326
332
|
function getSemanticSuffix(source: SchemaSourceMode): string {
|
|
327
|
-
|
|
328
|
-
case 'schemas':
|
|
329
|
-
return 'Schema'
|
|
330
|
-
case 'responses':
|
|
331
|
-
return 'Response'
|
|
332
|
-
case 'requestBodies':
|
|
333
|
-
return 'Request'
|
|
334
|
-
}
|
|
333
|
+
return semanticSuffixes[source]
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
/**
|
package/src/parser.ts
CHANGED
|
@@ -367,6 +367,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
367
367
|
|
|
368
368
|
// When a child schema extends a discriminator parent via allOf and the parent's oneOf/anyOf
|
|
369
369
|
// references that child back, skip that allOf item to prevent a circular type reference.
|
|
370
|
+
// When an item is skipped, collect its discriminant value so it can be injected below.
|
|
371
|
+
const filteredDiscriminantValues: Array<{ propertyName: string; value: string }> = []
|
|
370
372
|
const allOfMembers: Array<SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
|
|
371
373
|
.filter((item) => {
|
|
372
374
|
if (!isReference(item) || !name) return true
|
|
@@ -377,12 +379,18 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
377
379
|
const childRef = `#/components/schemas/${name}`
|
|
378
380
|
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
379
381
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
380
|
-
|
|
382
|
+
if (inOneOf || inMapping) {
|
|
383
|
+
const discriminatorValue = Object.entries(deref.discriminator.mapping ?? {}).find(([, v]) => v === childRef)?.[0]
|
|
384
|
+
if (discriminatorValue) {
|
|
385
|
+
filteredDiscriminantValues.push({ propertyName: deref.discriminator.propertyName, value: discriminatorValue })
|
|
386
|
+
}
|
|
387
|
+
return false
|
|
388
|
+
}
|
|
389
|
+
return true
|
|
381
390
|
})
|
|
382
391
|
.map((s) => convertSchema({ schema: s as SchemaObject }, options))
|
|
383
392
|
|
|
384
|
-
// Track where allOf-derived members end so
|
|
385
|
-
// (injected required-key objects + outer-properties object) are candidates for merging.
|
|
393
|
+
// Track where allOf-derived members end so each portion can be merged independently.
|
|
386
394
|
const syntheticStart = allOfMembers.length
|
|
387
395
|
|
|
388
396
|
// When `required` lists keys not present in the outer `properties`, resolve them from
|
|
@@ -414,10 +422,32 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
414
422
|
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf }, options))
|
|
415
423
|
}
|
|
416
424
|
|
|
425
|
+
// Inject a synthetic single-property object for each discriminant value collected from
|
|
426
|
+
// filtered discriminator parents so that child schemas carry the narrowed literal type.
|
|
427
|
+
for (const { propertyName, value } of filteredDiscriminantValues) {
|
|
428
|
+
allOfMembers.push(
|
|
429
|
+
createSchema({
|
|
430
|
+
type: 'object',
|
|
431
|
+
primitive: 'object',
|
|
432
|
+
properties: [
|
|
433
|
+
createProperty({
|
|
434
|
+
name: propertyName,
|
|
435
|
+
schema: createSchema({
|
|
436
|
+
type: 'enum',
|
|
437
|
+
primitive: 'string',
|
|
438
|
+
enumValues: [value],
|
|
439
|
+
}),
|
|
440
|
+
required: true,
|
|
441
|
+
}),
|
|
442
|
+
],
|
|
443
|
+
}),
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
|
|
417
447
|
// Merge consecutive anonymous object members within the synthetic portion — see `mergeAdjacentAnonymousObjects`.
|
|
418
448
|
return createSchema({
|
|
419
449
|
type: 'intersection',
|
|
420
|
-
members: [...allOfMembers.slice(0, syntheticStart), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
|
|
450
|
+
members: [...mergeAdjacentAnonymousObjects(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
|
|
421
451
|
...renderSchemaBase(schema, name, nullable, defaultValue),
|
|
422
452
|
})
|
|
423
453
|
}
|
|
@@ -471,6 +501,45 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
471
501
|
})
|
|
472
502
|
}
|
|
473
503
|
|
|
504
|
+
// When a discriminator with mapping is present but there are no sibling properties,
|
|
505
|
+
// embed the narrowed discriminant value into each member to produce precise literal
|
|
506
|
+
// intersection types (e.g. `Cat & { type: 'cat' }`) instead of plain `Cat | Dog`.
|
|
507
|
+
const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
|
|
508
|
+
if (discriminator?.mapping) {
|
|
509
|
+
return createSchema({
|
|
510
|
+
type: 'union',
|
|
511
|
+
...unionBase,
|
|
512
|
+
members: unionMembers.map((s) => {
|
|
513
|
+
const ref = isReference(s) ? s.$ref : undefined
|
|
514
|
+
const discriminatorValue = ref ? Object.entries(discriminator.mapping!).find(([, v]) => v === ref)?.[0] : undefined
|
|
515
|
+
const memberNode = convertSchema({ schema: s as SchemaObject }, options)
|
|
516
|
+
|
|
517
|
+
if (!discriminatorValue) return memberNode
|
|
518
|
+
|
|
519
|
+
const discriminantNode = createSchema({
|
|
520
|
+
type: 'object',
|
|
521
|
+
primitive: 'object',
|
|
522
|
+
properties: [
|
|
523
|
+
createProperty({
|
|
524
|
+
name: discriminator.propertyName,
|
|
525
|
+
schema: createSchema({
|
|
526
|
+
type: 'enum',
|
|
527
|
+
primitive: 'string',
|
|
528
|
+
enumValues: [discriminatorValue],
|
|
529
|
+
}),
|
|
530
|
+
required: true,
|
|
531
|
+
}),
|
|
532
|
+
],
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
return createSchema({
|
|
536
|
+
type: 'intersection',
|
|
537
|
+
members: [memberNode, discriminantNode],
|
|
538
|
+
})
|
|
539
|
+
}),
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
|
|
474
543
|
return createSchema({
|
|
475
544
|
type: 'union',
|
|
476
545
|
...unionBase,
|
|
@@ -487,6 +556,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
487
556
|
const constValue = schema.const
|
|
488
557
|
|
|
489
558
|
if (constValue === null) {
|
|
559
|
+
// Do not propagate `nullable` here: the type is already `null`, so marking it
|
|
560
|
+
// nullable too would cause the printer to emit `null | null`.
|
|
490
561
|
return createSchema({
|
|
491
562
|
type: 'null',
|
|
492
563
|
primitive: 'null',
|
|
@@ -494,7 +565,6 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
494
565
|
title: schema.title,
|
|
495
566
|
description: schema.description,
|
|
496
567
|
deprecated: schema.deprecated,
|
|
497
|
-
nullable,
|
|
498
568
|
})
|
|
499
569
|
}
|
|
500
570
|
|
|
@@ -719,15 +789,21 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
719
789
|
|
|
720
790
|
const childName = resolveChildName(name, propName)
|
|
721
791
|
const propNode = convertSchema({ schema: resolvedPropSchema, name: childName }, options)
|
|
722
|
-
|
|
792
|
+
let schemaNode = applyEnumName(propNode, name, propName, mergedOptions.enumSuffix)
|
|
793
|
+
|
|
794
|
+
const tupleNode = narrowSchema(schemaNode, 'tuple')
|
|
795
|
+
if (tupleNode?.items) {
|
|
796
|
+
const namedItems = tupleNode.items.map((item) => applyEnumName(item, name, propName, mergedOptions.enumSuffix))
|
|
797
|
+
if (namedItems.some((item, i) => item !== tupleNode.items![i])) {
|
|
798
|
+
schemaNode = { ...tupleNode, items: namedItems }
|
|
799
|
+
}
|
|
800
|
+
}
|
|
723
801
|
|
|
724
802
|
return createProperty({
|
|
725
803
|
name: propName,
|
|
726
804
|
schema: {
|
|
727
805
|
...schemaNode,
|
|
728
|
-
nullable: propNullable || undefined,
|
|
729
|
-
optional: !required && !propNullable ? true : undefined,
|
|
730
|
-
nullish: !required && propNullable ? true : undefined,
|
|
806
|
+
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
731
807
|
},
|
|
732
808
|
required,
|
|
733
809
|
})
|
|
@@ -783,12 +859,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
783
859
|
/**
|
|
784
860
|
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
785
861
|
*
|
|
786
|
-
* Each `prefixItems` element maps to a positional tuple slot.
|
|
787
|
-
*
|
|
862
|
+
* Each `prefixItems` element maps to a positional tuple slot. When an explicit `items` schema
|
|
863
|
+
* is present alongside `prefixItems`, it becomes the rest element. When `items` is absent,
|
|
864
|
+
* a rest element of type `any` is emitted to match JSON Schema semantics (absent `items`
|
|
865
|
+
* means additional items are allowed).
|
|
788
866
|
*/
|
|
789
867
|
function convertTuple({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
790
868
|
const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject }, options))
|
|
791
|
-
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) :
|
|
869
|
+
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) : createSchema({ type: 'any' })
|
|
792
870
|
|
|
793
871
|
return createSchema({
|
|
794
872
|
type: 'tuple',
|
|
@@ -983,15 +1061,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
983
1061
|
|
|
984
1062
|
/**
|
|
985
1063
|
* Converts a single dereferenced OAS parameter object into a `ParameterNode`.
|
|
986
|
-
* When the parameter has no `schema
|
|
1064
|
+
* When the parameter has no `schema`, falls back to `unknownType`; `$ref` schemas are resolved through `convertSchema` to produce a proper named type reference.
|
|
987
1065
|
*/
|
|
988
1066
|
function parseParameter(options: ParserOptions, param: Record<string, unknown>): ParameterNode {
|
|
989
1067
|
const required = (param['required'] as boolean | undefined) ?? false
|
|
990
1068
|
|
|
991
|
-
const schema: SchemaNode =
|
|
992
|
-
param['schema']
|
|
993
|
-
|
|
994
|
-
: createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
1069
|
+
const schema: SchemaNode = param['schema']
|
|
1070
|
+
? convertSchema({ schema: param['schema'] as SchemaObject }, options)
|
|
1071
|
+
: createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
995
1072
|
|
|
996
1073
|
return createParameter({
|
|
997
1074
|
name: param['name'] as string,
|
|
@@ -999,7 +1076,6 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
999
1076
|
schema: {
|
|
1000
1077
|
...schema,
|
|
1001
1078
|
description: (param['description'] as string | undefined) ?? schema.description,
|
|
1002
|
-
optional: !required || !!schema.optional ? true : undefined,
|
|
1003
1079
|
},
|
|
1004
1080
|
required,
|
|
1005
1081
|
})
|
|
@@ -1015,6 +1091,11 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1015
1091
|
const requestBodySchema = oas.getRequestSchema(operation)
|
|
1016
1092
|
const requestBodySchemaNode = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : undefined
|
|
1017
1093
|
|
|
1094
|
+
const requestBodyDescription =
|
|
1095
|
+
operation.schema.requestBody && !isReference(operation.schema.requestBody)
|
|
1096
|
+
? (operation.schema.requestBody as { description?: string }).description
|
|
1097
|
+
: undefined
|
|
1098
|
+
|
|
1018
1099
|
const requestBodyKeysToOmit = requestBodySchema?.properties
|
|
1019
1100
|
? Object.entries(requestBodySchema.properties)
|
|
1020
1101
|
.filter(([, prop]) => !isReference(prop) && (prop as { readOnly?: boolean }).readOnly)
|
|
@@ -1023,6 +1104,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1023
1104
|
|
|
1024
1105
|
const requestBody = requestBodySchemaNode
|
|
1025
1106
|
? {
|
|
1107
|
+
description: requestBodyDescription,
|
|
1026
1108
|
schema: requestBodySchemaNode,
|
|
1027
1109
|
keysToOmit: requestBodyKeysToOmit?.length ? requestBodyKeysToOmit : undefined,
|
|
1028
1110
|
}
|