@kubb/adapter-oas 5.0.0-alpha.12 → 5.0.0-alpha.13
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 +83 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +83 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +2 -3
- package/src/parser.ts +100 -15
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.13",
|
|
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.13",
|
|
50
|
+
"@kubb/core": "5.0.0-alpha.13"
|
|
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/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,13 +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,
|
|
806
|
+
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
729
807
|
optional: !required && !propNullable ? true : undefined,
|
|
730
808
|
nullish: !required && propNullable ? true : undefined,
|
|
731
809
|
},
|
|
@@ -783,12 +861,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
783
861
|
/**
|
|
784
862
|
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
785
863
|
*
|
|
786
|
-
* Each `prefixItems` element maps to a positional tuple slot.
|
|
787
|
-
*
|
|
864
|
+
* Each `prefixItems` element maps to a positional tuple slot. When an explicit `items` schema
|
|
865
|
+
* is present alongside `prefixItems`, it becomes the rest element. When `items` is absent,
|
|
866
|
+
* a rest element of type `any` is emitted to match JSON Schema semantics (absent `items`
|
|
867
|
+
* means additional items are allowed).
|
|
788
868
|
*/
|
|
789
869
|
function convertTuple({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
790
870
|
const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject }, options))
|
|
791
|
-
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) :
|
|
871
|
+
const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) : createSchema({ type: 'any' })
|
|
792
872
|
|
|
793
873
|
return createSchema({
|
|
794
874
|
type: 'tuple',
|
|
@@ -983,15 +1063,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
983
1063
|
|
|
984
1064
|
/**
|
|
985
1065
|
* Converts a single dereferenced OAS parameter object into a `ParameterNode`.
|
|
986
|
-
* When the parameter has no `schema
|
|
1066
|
+
* When the parameter has no `schema`, falls back to `unknownType`; `$ref` schemas are resolved through `convertSchema` to produce a proper named type reference.
|
|
987
1067
|
*/
|
|
988
1068
|
function parseParameter(options: ParserOptions, param: Record<string, unknown>): ParameterNode {
|
|
989
1069
|
const required = (param['required'] as boolean | undefined) ?? false
|
|
990
1070
|
|
|
991
|
-
const schema: SchemaNode =
|
|
992
|
-
param['schema']
|
|
993
|
-
|
|
994
|
-
: createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
1071
|
+
const schema: SchemaNode = param['schema']
|
|
1072
|
+
? convertSchema({ schema: param['schema'] as SchemaObject }, options)
|
|
1073
|
+
: createSchema({ type: resolveTypeOption(options.unknownType) })
|
|
995
1074
|
|
|
996
1075
|
return createParameter({
|
|
997
1076
|
name: param['name'] as string,
|
|
@@ -1015,6 +1094,11 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1015
1094
|
const requestBodySchema = oas.getRequestSchema(operation)
|
|
1016
1095
|
const requestBodySchemaNode = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : undefined
|
|
1017
1096
|
|
|
1097
|
+
const requestBodyDescription =
|
|
1098
|
+
operation.schema.requestBody && !isReference(operation.schema.requestBody)
|
|
1099
|
+
? (operation.schema.requestBody as { description?: string }).description
|
|
1100
|
+
: undefined
|
|
1101
|
+
|
|
1018
1102
|
const requestBodyKeysToOmit = requestBodySchema?.properties
|
|
1019
1103
|
? Object.entries(requestBodySchema.properties)
|
|
1020
1104
|
.filter(([, prop]) => !isReference(prop) && (prop as { readOnly?: boolean }).readOnly)
|
|
@@ -1023,6 +1107,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
|
|
|
1023
1107
|
|
|
1024
1108
|
const requestBody = requestBodySchemaNode
|
|
1025
1109
|
? {
|
|
1110
|
+
description: requestBodyDescription,
|
|
1026
1111
|
schema: requestBodySchemaNode,
|
|
1027
1112
|
keysToOmit: requestBodyKeysToOmit?.length ? requestBodyKeysToOmit : undefined,
|
|
1028
1113
|
}
|