@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/dist/index.cjs +271 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +272 -114
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/adapter.ts +19 -2
- package/src/constants.ts +9 -5
- package/src/oas/Oas.ts +16 -9
- package/src/oas/types.ts +19 -0
- package/src/oas/utils.ts +1 -1
- package/src/parser.ts +115 -159
- package/src/types.ts +6 -0
- package/src/utils.ts +168 -0
package/src/parser.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
import { pascalCase } from '@internals/utils'
|
|
2
|
-
import {
|
|
3
|
-
collect,
|
|
4
|
-
createOperation,
|
|
5
|
-
createParameter,
|
|
6
|
-
createProperty,
|
|
7
|
-
createResponse,
|
|
8
|
-
createRoot,
|
|
9
|
-
createSchema,
|
|
10
|
-
narrowSchema,
|
|
11
|
-
schemaTypes,
|
|
12
|
-
transform,
|
|
13
|
-
} from '@kubb/ast'
|
|
1
|
+
import { 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.
|
|
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
|
-
*
|
|
250
|
-
*
|
|
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
|
-
*
|
|
258
|
-
*
|
|
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
|
-
|
|
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(
|
|
390
|
-
ref:
|
|
338
|
+
name: extractRefName(schema.$ref!),
|
|
339
|
+
ref: schema.$ref,
|
|
391
340
|
nullable,
|
|
392
|
-
description:
|
|
393
|
-
deprecated:
|
|
394
|
-
readOnly:
|
|
395
|
-
writeOnly:
|
|
396
|
-
pattern:
|
|
397
|
-
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
|
|
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>(
|
|
399
|
+
const deref = oas.get<SchemaObject>(item.$ref)
|
|
451
400
|
if (!deref || !isDiscriminator(deref)) return true
|
|
452
|
-
const parentUnion =
|
|
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) &&
|
|
456
|
-
const inMapping = Object.values(
|
|
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
|
|
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
|
|
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
|
|
489
|
-
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf
|
|
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
|
|
516
|
-
const
|
|
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
|
-
|
|
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
|
|
623
|
-
const
|
|
624
|
-
const
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
|
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
|
-
...
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
787
|
-
const
|
|
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
|
|
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 =
|
|
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
|
|
902
|
-
if (flattenedSchema && flattenedSchema !==
|
|
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' &&
|
|
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
|
|
988
|
-
|
|
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
|
-
|
|
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> =
|
|
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 =
|
|
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
|
|
1087
|
+
parse,
|
|
1132
1088
|
convertSchema,
|
|
1133
1089
|
resolveRefs,
|
|
1134
|
-
|
|
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>
|