@kubb/adapter-oas 4.36.1 → 5.0.0-alpha.10

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/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 { 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,11 @@ import type {
38
27
  TimeSchemaNode,
39
28
  UnionSchemaNode,
40
29
  } from '@kubb/ast/types'
41
- import type { KubbFile } from '@kubb/fabric-core/types'
42
30
  import { 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 { applyDiscriminatorEnum, extractRefName, mergeAdjacentAnonymousObjects, simplifyUnionMembers } from './utils.ts'
46
35
 
47
36
  /**
48
37
  * Distributive `Omit` — correctly distributes over union types so that
@@ -178,15 +167,6 @@ function formatToSchemaType(format: string): SchemaType | undefined {
178
167
  return formatMap[format as keyof typeof formatMap]
179
168
  }
180
169
 
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
170
  /**
191
171
  * Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
192
172
  * Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
@@ -203,7 +183,7 @@ function getPrimitiveType(type: string | undefined): PrimitiveSchemaType {
203
183
  * Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
204
184
  */
205
185
  function toMediaType(contentType: string): MediaType | undefined {
206
- return knownMediaTypes.includes(contentType as MediaType) ? (contentType as MediaType) : undefined
186
+ return knownMediaTypes.has(contentType as MediaType) ? (contentType as MediaType) : undefined
207
187
  }
208
188
 
209
189
  /**
@@ -245,45 +225,15 @@ export type OasParser = {
245
225
  * the transformed name to use (e.g. with a plugin `transformers.name` applied).
246
226
  */
247
227
  resolveRefs: (node: SchemaNode, resolveName: (ref: string) => string | undefined, resolveEnumName?: (name: string) => string | undefined) => SchemaNode
228
+
248
229
  /**
249
- * Extracts `KubbFile.Import` entries from a `SchemaNode` tree by collecting
250
- * all importable `ref` nodes. A `$ref` is considered importable when it resolves
251
- * to a known component in the OAS spec (`oas.get($ref)` is truthy).
252
- *
253
- * The `resolve` callback is called with the schema name (last segment of the
254
- * `$ref`, collision-corrected via the OAS name mapping) and must return the
255
- * `{ name, path }` pair for the generated import, or `undefined` to skip it.
230
+ * Map from original `$ref` paths to their collision-resolved schema names.
231
+ * e.g. `'#/components/schemas/Order'` `'OrderSchema'`
256
232
  *
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
- * ```
233
+ * Pass this to the standalone `getImports()` to resolve imports without holding
234
+ * a reference to the full parser or OAS instance.
264
235
  */
265
- getImports: (node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string } | undefined) => Array<KubbFile.Import>
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
236
+ nameMapping: Map<string, string>
287
237
  }
288
238
 
289
239
  /**
@@ -383,18 +333,17 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
383
333
  * reflected in generated JSDoc and type modifiers.
384
334
  */
385
335
  function convertRef({ schema, nullable, defaultValue }: SchemaContext): SchemaNode {
386
- const schemaObject = schema as unknown as SchemaObject & { $ref: string }
387
336
  return createSchema({
388
337
  type: 'ref',
389
- name: extractRefName(schemaObject.$ref),
390
- ref: schemaObject.$ref,
338
+ name: extractRefName(schema.$ref!),
339
+ ref: schema.$ref,
391
340
  nullable,
392
- description: schemaObject.description,
393
- deprecated: schemaObject.deprecated,
394
- readOnly: schemaObject.readOnly,
395
- writeOnly: schemaObject.writeOnly,
396
- pattern: schemaObject.type === 'string' ? schemaObject.pattern : undefined,
397
- example: schemaObject.example,
341
+ description: schema.description,
342
+ deprecated: schema.deprecated,
343
+ readOnly: schema.readOnly,
344
+ writeOnly: schema.writeOnly,
345
+ pattern: schema.type === 'string' ? schema.pattern : undefined,
346
+ example: schema.example,
398
347
  default: defaultValue,
399
348
  })
400
349
  }
@@ -421,8 +370,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
421
370
  !(Array.isArray(schema.required) && schema.required.length) &&
422
371
  schema.additionalProperties === undefined
423
372
  ) {
424
- const [memberSchema] = schema.allOf as SchemaObject[]
425
- const memberNode = convertSchema({ schema: memberSchema! }, options)
373
+ const [memberSchema] = schema.allOf as Array<SchemaObject | ReferenceObject>
374
+ const memberNode = convertSchema({ schema: memberSchema! as SchemaObject }, options)
426
375
  const { kind: _kind, ...memberNodeProps } = memberNode
427
376
  const mergedNullable = nullable || memberNode.nullable || undefined
428
377
  const mergedDefault = schema.default === null && mergedNullable ? undefined : (schema.default ?? memberNode.default)
@@ -444,22 +393,24 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
444
393
 
445
394
  // When a child schema extends a discriminator parent via allOf and the parent's oneOf/anyOf
446
395
  // references that child back, skip that allOf item to prevent a circular type reference.
447
- const allOfMembers: SchemaNode[] = (schema.allOf as SchemaObject[])
396
+ const allOfMembers: Array<SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
448
397
  .filter((item) => {
449
398
  if (!isReference(item) || !name) return true
450
- const deref = oas.get<SchemaObject>((item as { $ref: string }).$ref)
399
+ const deref = oas.get<SchemaObject>(item.$ref)
451
400
  if (!deref || !isDiscriminator(deref)) return true
452
- const parentUnion = (deref as SchemaObject).oneOf ?? (deref as SchemaObject).anyOf
401
+ const parentUnion = deref.oneOf ?? deref.anyOf
453
402
  if (!parentUnion) return true
454
403
  const childRef = `#/components/schemas/${name}`
455
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && (oneOfItem as { $ref: string }).$ref === childRef)
456
- const inMapping = Object.values((deref as SchemaObject & { discriminator: { mapping?: Record<string, string> } }).discriminator.mapping ?? {}).some(
457
- (v) => v === childRef,
458
- )
404
+ const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
405
+ const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
459
406
  return !inOneOf && !inMapping
460
407
  })
461
408
  .map((s) => convertSchema({ schema: s as SchemaObject }, options))
462
409
 
410
+ // Track where allOf-derived members end so only the synthetic members added below
411
+ // (injected required-key objects + outer-properties object) are candidates for merging.
412
+ const syntheticStart = allOfMembers.length
413
+
463
414
  // When `required` lists keys not present in the outer `properties`, resolve them from
464
415
  // the allOf member schemas and inject them as extra intersection members.
465
416
  if (Array.isArray(schema.required) && schema.required.length) {
@@ -467,10 +418,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
467
418
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key))
468
419
 
469
420
  if (missingRequired.length) {
470
- const resolvedMembers = (schema.allOf as SchemaObject[]).flatMap((item) => {
471
- if (!isReference(item)) return [item]
421
+ const resolvedMembers = (schema.allOf as Array<SchemaObject | ReferenceObject>).flatMap((item) => {
422
+ if (!isReference(item)) return [item as SchemaObject]
472
423
  const deref = oas.get<SchemaObject>(item.$ref)
473
- return deref && !isReference(deref) ? [deref as SchemaObject] : []
424
+ return deref && !isReference(deref) ? [deref] : []
474
425
  })
475
426
 
476
427
  for (const key of missingRequired) {
@@ -485,13 +436,14 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
485
436
  }
486
437
 
487
438
  if (schema.properties) {
488
- const { allOf: _allOf, ...schemaWithoutAllOf } = schema as SchemaObject & { allOf?: unknown[] }
489
- allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf as SchemaObject }, options))
439
+ const { allOf: _allOf, ...schemaWithoutAllOf } = schema
440
+ allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf }, options))
490
441
  }
491
442
 
443
+ // Merge consecutive anonymous object members within the synthetic portion — see `mergeAdjacentAnonymousObjects`.
492
444
  return createSchema({
493
445
  type: 'intersection',
494
- members: allOfMembers,
446
+ members: [...allOfMembers.slice(0, syntheticStart), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
495
447
  ...buildSchemaBase(schema, name, nullable, defaultValue),
496
448
  })
497
449
  }
@@ -512,25 +464,39 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
512
464
  }
513
465
 
514
466
  if (schema.properties) {
515
- const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema as SchemaObject & { oneOf?: unknown[]; anyOf?: unknown[] }
516
- const propertiesNode = convertSchema({ schema: schemaWithoutUnion as SchemaObject }, options)
467
+ const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
468
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
469
+
470
+ // Strip discriminator so convertObject won't re-apply the full mapping enum.
471
+ const memberBaseSchema: SchemaObject = discriminator
472
+ ? (Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== 'discriminator')) as SchemaObject)
473
+ : schemaWithoutUnion
517
474
 
518
475
  return createSchema({
519
476
  type: 'union',
520
477
  ...unionBase,
521
- members: unionMembers.map((s) =>
522
- createSchema({
478
+ members: unionMembers.map((s) => {
479
+ const ref = isReference(s) ? s.$ref : undefined
480
+ const discriminatorValue = discriminator?.mapping && ref ? Object.entries(discriminator.mapping).find(([, v]) => v === ref)?.[0] : undefined
481
+
482
+ let propertiesNode = convertSchema({ schema: memberBaseSchema, name }, options)
483
+
484
+ if (discriminatorValue && discriminator) {
485
+ propertiesNode = applyDiscriminatorEnum({ node: propertiesNode, propertyName: discriminator.propertyName, values: [discriminatorValue] })
486
+ }
487
+
488
+ return createSchema({
523
489
  type: 'intersection',
524
490
  members: [convertSchema({ schema: s as SchemaObject }, options), propertiesNode],
525
- }),
526
- ),
491
+ })
492
+ }),
527
493
  })
528
494
  }
529
495
 
530
496
  return createSchema({
531
497
  type: 'union',
532
498
  ...unionBase,
533
- members: unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options)),
499
+ members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options))),
534
500
  })
535
501
  }
536
502
 
@@ -603,6 +569,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
603
569
  if (specialType === 'number' || specialType === 'integer' || specialType === 'bigint') {
604
570
  return createSchema({ ...base, primitive: specialPrimitive, type: specialType })
605
571
  }
572
+ if (specialType === 'url') {
573
+ return createSchema({ ...base, primitive: 'string' as const, type: 'url' })
574
+ }
575
+
606
576
  return createSchema({ ...base, primitive: specialPrimitive, type: specialType as ScalarSchemaType })
607
577
  }
608
578
 
@@ -619,10 +589,9 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
619
589
  function convertEnum({ schema, name, nullable, type, options }: SchemaContext): SchemaNode {
620
590
  // Malformed schema: `{ type: 'array', enum: [...] }` — normalize by moving the enum into items.
621
591
  if (type === 'array') {
622
- const rawSchema = schema as unknown as { items?: SchemaObject; enum?: unknown[] }
623
- const isItemsObject = typeof rawSchema.items === 'object' && !Array.isArray(rawSchema.items)
624
- const normalizedItems = { ...(isItemsObject ? rawSchema.items : {}), enum: schema.enum } as SchemaObject
625
- const { enum: _enum, ...schemaWithoutEnum } = schema as SchemaObject & { enum?: unknown[] }
592
+ const isItemsObject = typeof schema.items === 'object' && !Array.isArray(schema.items)
593
+ const normalizedItems: SchemaObject = { ...(isItemsObject ? (schema.items as SchemaObject) : {}), enum: schema.enum }
594
+ const { enum: _enum, ...schemaWithoutEnum } = schema
626
595
  return convertSchema({ schema: { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject, name }, options)
627
596
  }
628
597
 
@@ -716,21 +685,21 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
716
685
  * - not required + nullable → `nullish: true`
717
686
  */
718
687
  function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
719
- // When a discriminator is present, override the discriminator property's schema to use
720
- // an enum of the mapping keys for a precise literal-union type.
721
- const resolvedSchema = applyDiscriminatorEnum(schema)
722
-
723
- const properties: Array<PropertyNode> = resolvedSchema.properties
724
- ? Object.entries(resolvedSchema.properties).map(([propName, propSchema]) => {
725
- const required = Array.isArray(resolvedSchema.required) ? resolvedSchema.required.includes(propName) : !!resolvedSchema.required
688
+ const properties: Array<PropertyNode> = schema.properties
689
+ ? Object.entries(schema.properties).map(([propName, propSchema]) => {
690
+ const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
726
691
  const resolvedPropSchema = propSchema as SchemaObject
727
692
  const propNullable = isNullable(resolvedPropSchema)
728
- const derivedPropName = name ? pascalCase([name, propName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : undefined
693
+ const basePropName = name ? pascalCase([name, propName].join(' ')) : undefined
694
+ const propNode = convertSchema({ schema: resolvedPropSchema, name: basePropName }, options)
695
+ const isEnumNode = !!narrowSchema(propNode, 'enum')
696
+ const derivedPropName = isEnumNode && name ? pascalCase([name, propName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : basePropName
697
+ const schemaNode = isEnumNode && derivedPropName !== basePropName ? { ...propNode, name: derivedPropName } : propNode
729
698
 
730
699
  return createProperty({
731
700
  name: propName,
732
701
  schema: {
733
- ...convertSchema({ schema: resolvedPropSchema, name: derivedPropName }, options),
702
+ ...schemaNode,
734
703
  nullable: propNullable || undefined,
735
704
  optional: !required && !propNullable ? true : undefined,
736
705
  nullish: !required && propNullable ? true : undefined,
@@ -740,7 +709,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
740
709
  })
741
710
  : []
742
711
 
743
- const additionalProperties = resolvedSchema.additionalProperties
712
+ const additionalProperties = schema.additionalProperties
744
713
  let additionalPropertiesNode: SchemaNode | true | undefined
745
714
  if (additionalProperties === true) {
746
715
  additionalPropertiesNode = true
@@ -752,21 +721,20 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
752
721
  additionalPropertiesNode = createSchema({ type: resolveTypeOption(mergedOptions.unknownType) })
753
722
  }
754
723
 
755
- const rawPatternProperties =
756
- 'patternProperties' in resolvedSchema ? (resolvedSchema as unknown as { patternProperties?: Record<string, SchemaObject> }).patternProperties : undefined
724
+ const rawPatternProperties = 'patternProperties' in schema ? schema.patternProperties : undefined
757
725
 
758
726
  const patternProperties = rawPatternProperties
759
727
  ? Object.fromEntries(
760
728
  Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [
761
729
  pattern,
762
- (patternSchema as unknown) === true || Object.keys(patternSchema as object).length === 0
730
+ patternSchema === true || (typeof patternSchema === 'object' && Object.keys(patternSchema).length === 0)
763
731
  ? createSchema({ type: resolveTypeOption(mergedOptions.unknownType) })
764
732
  : convertSchema({ schema: patternSchema as SchemaObject }, options),
765
733
  ]),
766
734
  )
767
735
  : undefined
768
736
 
769
- return createSchema({
737
+ const objectNode: SchemaNode = createSchema({
770
738
  type: 'object',
771
739
  primitive: 'object',
772
740
  properties,
@@ -774,6 +742,17 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
774
742
  patternProperties,
775
743
  ...buildSchemaBase(schema, name, nullable, defaultValue),
776
744
  })
745
+
746
+ // When a discriminator is present, replace the discriminator property's schema
747
+ // with an enum of the mapping keys for a precise literal-union type.
748
+ if (isDiscriminator(schema) && schema.discriminator.mapping) {
749
+ const discPropName = schema.discriminator.propertyName
750
+ const values = Object.keys(schema.discriminator.mapping)
751
+ const enumName = name ? pascalCase([name, discPropName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : undefined
752
+ return applyDiscriminatorEnum({ node: objectNode, propertyName: discPropName, values, enumName })
753
+ }
754
+
755
+ return objectNode
777
756
  }
778
757
 
779
758
  /**
@@ -783,9 +762,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
783
762
  * after the prefix items is mapped to the rest parameter of the tuple.
784
763
  */
785
764
  function convertTuple({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
786
- const rawSchema = schema as unknown as { prefixItems: SchemaObject[]; items?: SchemaObject }
787
- const tupleItems = rawSchema.prefixItems.map((item) => convertSchema({ schema: item }, options))
788
- const rest = rawSchema.items ? convertSchema({ schema: rawSchema.items }, options) : undefined
765
+ const tupleItems = (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item as SchemaObject }, options))
766
+ const rest = schema.items ? convertSchema({ schema: schema.items as SchemaObject }, options) : undefined
789
767
 
790
768
  return createSchema({
791
769
  type: 'tuple',
@@ -805,12 +783,11 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
805
783
  * `enumSuffix` is forwarded so generators can emit a named enum declaration.
806
784
  */
807
785
  function convertArray({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
808
- const rawSchema = schema as unknown as { items?: SchemaObject }
786
+ const rawItems = schema.items as SchemaObject | undefined
809
787
  // When the array items schema contains an inline enum, derive a name from the parent
810
788
  // array's name + enumSuffix so generators can emit a named enum declaration.
811
- const rawItems = rawSchema.items as SchemaObject | undefined
812
789
  const itemName = rawItems?.enum?.length && name ? pascalCase([name, mergedOptions.enumSuffix].join(' ')) : undefined
813
- const items = rawSchema.items ? [convertSchema({ schema: rawSchema.items, name: itemName }, options)] : []
790
+ const items = rawItems ? [convertSchema({ schema: rawItems, name: itemName }, options)] : []
814
791
 
815
792
  return createSchema({
816
793
  type: 'array',
@@ -898,8 +875,8 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
898
875
  const mergedOptions: Options = { ...DEFAULT_OPTIONS, ...options }
899
876
  // Flatten keyword-only allOf fragments (no $ref, no structural keys) into the parent
900
877
  // schema before parsing, so simple annotation patterns don't produce needless intersections.
901
- const flattenedSchema = flattenSchema(schema as unknown as Parameters<typeof flattenSchema>[0]) as SchemaObject | null
902
- if (flattenedSchema && flattenedSchema !== (schema as unknown)) {
878
+ const flattenedSchema = flattenSchema(schema)
879
+ if (flattenedSchema && flattenedSchema !== schema) {
903
880
  return convertSchema({ schema: flattenedSchema, name }, options)
904
881
  }
905
882
 
@@ -933,7 +910,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
933
910
  }
934
911
 
935
912
  // OAS 3.1: `contentMediaType: 'application/octet-stream'` on a string schema signals binary data.
936
- if (schema.type === 'string' && (schema as SchemaObject & { contentMediaType?: string }).contentMediaType === 'application/octet-stream') {
913
+ if (schema.type === 'string' && schema.contentMediaType === 'application/octet-stream') {
937
914
  return createSchema({ type: 'blob', primitive: 'string', ...buildSchemaBase(schema, name, nullable, defaultValue) })
938
915
  }
939
916
 
@@ -947,7 +924,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
947
924
  if (nonNullTypes.length > 1) {
948
925
  return createSchema({
949
926
  type: 'union',
950
- members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject }, options)),
927
+ members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject, name }, options)),
951
928
  ...buildSchemaBase(schema, name, arrayNullable, defaultValue),
952
929
  })
953
930
  }
@@ -984,16 +961,22 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
984
961
  * When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
985
962
  */
986
963
  function parseParameter(options: Options, param: Record<string, unknown>): ParameterNode {
987
- const schema =
988
- param['schema'] && !isReference(param['schema'] as object)
964
+ const required = (param['required'] as boolean | undefined) ?? false
965
+
966
+ const schema: SchemaNode =
967
+ param['schema'] && !isReference(param['schema'])
989
968
  ? convertSchema({ schema: param['schema'] as SchemaObject }, options)
990
969
  : createSchema({ type: resolveTypeOption(options.unknownType) })
991
970
 
992
971
  return createParameter({
993
972
  name: param['name'] as string,
994
973
  in: param['in'] as ParameterLocation,
995
- schema,
996
- required: (param['required'] as boolean | undefined) ?? false,
974
+ schema: {
975
+ ...schema,
976
+ description: (param['description'] as string | undefined) ?? schema.description,
977
+ optional: !required || !!schema.optional ? true : undefined,
978
+ },
979
+ required,
997
980
  })
998
981
  }
999
982
 
@@ -1002,11 +985,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
1002
985
  * request body, and all response codes into their AST node equivalents.
1003
986
  */
1004
987
  function parseOperation(options: Options, oas: Oas, operation: Operation): OperationNode {
1005
- const parameters: Array<ParameterNode> = operation.getParameters().map((param) => {
1006
- const dereferenced = oas.dereferenceWithRef(param) as unknown as Record<string, unknown>
1007
-
1008
- return parseParameter(options, dereferenced)
1009
- })
988
+ const parameters: Array<ParameterNode> = oas.getParameters(operation).map((param) => parseParameter(options, param as unknown as Record<string, unknown>))
1010
989
 
1011
990
  const requestBodySchema = oas.getRequestSchema(operation)
1012
991
  const requestBody = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : undefined
@@ -1015,7 +994,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
1015
994
  const responseObj = operation.getResponseByStatusCode(statusCode)
1016
995
  const responseSchema = oas.getResponseSchema(operation, statusCode)
1017
996
 
1018
- const schema = responseSchema && Object.keys(responseSchema).length > 0 ? convertSchema({ schema: responseSchema }, options) : undefined
997
+ const schema =
998
+ responseSchema && Object.keys(responseSchema).length > 0
999
+ ? convertSchema({ schema: responseSchema }, options)
1000
+ : createSchema({ type: resolveTypeOption(options.emptySchemaType) })
1019
1001
 
1020
1002
  const description = typeof responseObj === 'object' && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : undefined
1021
1003
 
@@ -1037,7 +1019,7 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
1037
1019
  return createOperation({
1038
1020
  operationId: operation.getOperationId(),
1039
1021
  method: operation.method.toUpperCase() as HttpMethod,
1040
- path: operation.path,
1022
+ path: new URLPath(operation.path).URL,
1041
1023
  tags: operation.getTags().map((tag) => tag.name),
1042
1024
  summary: operation.getSummary() || undefined,
1043
1025
  description: operation.getDescription() || undefined,
@@ -1101,36 +1083,10 @@ export function createOasParser(oas: Oas, { contentType, collisionDetection }: O
1101
1083
  }) as SchemaNode
1102
1084
  }
1103
1085
 
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
1086
  return {
1131
- parse: parse,
1087
+ parse,
1132
1088
  convertSchema,
1133
1089
  resolveRefs,
1134
- getImports,
1090
+ nameMapping,
1135
1091
  } as OasParser
1136
1092
  }
package/src/types.ts CHANGED
@@ -82,6 +82,12 @@ export type OasAdapterResolvedOptions = {
82
82
  integerType: NonNullable<OasAdapterOptions['integerType']>
83
83
  unknownType: NonNullable<OasAdapterOptions['unknownType']>
84
84
  emptySchemaType: NonNullable<OasAdapterOptions['emptySchemaType']>
85
+ /**
86
+ * Map from original `$ref` paths to their collision-resolved schema names.
87
+ * Populated by the adapter after each `parse()` call.
88
+ * e.g. `'#/components/schemas/Order'` → `'OrderSchema'`
89
+ */
90
+ nameMapping: Map<string, string>
85
91
  }
86
92
 
87
93
  export type OasAdapter = AdapterFactoryOptions<'oas', OasAdapterOptions, OasAdapterResolvedOptions>