@kubb/adapter-oas 5.0.0-beta.60 → 5.0.0-beta.62
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 +121 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +122 -110
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/dialect.ts +28 -6
- package/src/parser.ts +98 -83
- package/src/stream.ts +28 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/adapter-oas",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.62",
|
|
4
4
|
"description": "OpenAPI and Swagger adapter for Kubb. Parses and validates OAS 2.0/3.x specifications into a @kubb/ast RootNode for downstream code generation plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"api-ref-bundler": "0.5.1",
|
|
47
47
|
"swagger2openapi": "^7.0.8",
|
|
48
48
|
"yaml": "^2.9.0",
|
|
49
|
-
"@kubb/ast": "5.0.0-beta.
|
|
50
|
-
"@kubb/core": "5.0.0-beta.
|
|
49
|
+
"@kubb/ast": "5.0.0-beta.62",
|
|
50
|
+
"@kubb/core": "5.0.0-beta.62"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/json-schema": "^7.0.15",
|
package/src/dialect.ts
CHANGED
|
@@ -3,6 +3,25 @@ import { isDiscriminator, isNullable, isReference } from './guards.ts'
|
|
|
3
3
|
import { resolveRef } from './refs.ts'
|
|
4
4
|
import type { SchemaObject } from './types.ts'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Derives a schema's `optional`/`nullish` flags from a parent's `required` value and the
|
|
8
|
+
* schema's own `nullable`. How "required" and "nullable" combine is OpenAPI-specific, so it
|
|
9
|
+
* lives in the dialect.
|
|
10
|
+
*
|
|
11
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
12
|
+
* - Non-required + nullable → `nullish: true`.
|
|
13
|
+
* - Required → both flags cleared.
|
|
14
|
+
*/
|
|
15
|
+
function optionality(schema: ast.SchemaNode, required: boolean): ast.SchemaNode {
|
|
16
|
+
const nullable = schema.nullable ?? false
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
...schema,
|
|
20
|
+
optional: !required && !nullable ? true : undefined,
|
|
21
|
+
nullish: !required && nullable ? true : undefined,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
6
25
|
/**
|
|
7
26
|
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
8
27
|
*
|
|
@@ -22,13 +41,16 @@ import type { SchemaObject } from './types.ts'
|
|
|
22
41
|
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
23
42
|
* ```
|
|
24
43
|
*/
|
|
25
|
-
export const oasDialect = ast.
|
|
44
|
+
export const oasDialect = ast.defineDialect({
|
|
26
45
|
name: 'oas',
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
schema: {
|
|
47
|
+
isNullable,
|
|
48
|
+
isReference,
|
|
49
|
+
isDiscriminator,
|
|
50
|
+
isBinary: (schema: SchemaObject) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
51
|
+
resolveRef,
|
|
52
|
+
optionality,
|
|
53
|
+
},
|
|
32
54
|
})
|
|
33
55
|
|
|
34
56
|
/**
|
package/src/parser.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { pascalCase } from '@internals/utils'
|
|
2
|
-
import {
|
|
2
|
+
import { macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion } from '@kubb/ast/macros'
|
|
3
|
+
import { childName, enumPropName, extractRefName, mergeAdjacentObjectsLazy } from '@kubb/ast/utils'
|
|
3
4
|
import { ast } from '@kubb/core'
|
|
4
5
|
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, SCHEMA_REF_PREFIX, typeOptionMap } from './constants.ts'
|
|
5
6
|
import { oasDialect, type OasDialect } from './dialect.ts'
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
getSchemaType,
|
|
20
21
|
} from './resolvers.ts'
|
|
21
22
|
import type { ContentType, Document, Operation, ReferenceObject, SchemaObject } from './types.ts'
|
|
23
|
+
import type { StatusCode } from '@kubb/ast'
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Parser context holding the raw OpenAPI document and optional content-type override.
|
|
@@ -100,6 +102,39 @@ function normalizeArrayEnum(schema: SchemaObject): SchemaObject {
|
|
|
100
102
|
return { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
|
|
107
|
+
* and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
|
|
108
|
+
*/
|
|
109
|
+
function createNullSchema(schema: SchemaObject, name: string | null | undefined): ast.SchemaNode {
|
|
110
|
+
return ast.factory.createSchema({
|
|
111
|
+
type: 'null',
|
|
112
|
+
primitive: 'null',
|
|
113
|
+
name,
|
|
114
|
+
title: schema.title,
|
|
115
|
+
description: schema.description,
|
|
116
|
+
deprecated: schema.deprecated,
|
|
117
|
+
format: schema.format,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Names the inline enums on a property's schema, and on each item when the property is a tuple, from
|
|
123
|
+
* the parent and property name. Wraps `macroEnumName` at the property construction site.
|
|
124
|
+
*/
|
|
125
|
+
function nameEnums(node: ast.SchemaNode, options: { parentName: string | null | undefined; propName: string; enumSuffix: string }): ast.SchemaNode {
|
|
126
|
+
const macro = macroEnumName(options)
|
|
127
|
+
const named = ast.applyMacros(node, [macro], { depth: 'shallow' })
|
|
128
|
+
const tupleNode = ast.narrowSchema(named, 'tuple')
|
|
129
|
+
if (tupleNode?.items) {
|
|
130
|
+
const namedItems = tupleNode.items.map((item) => ast.applyMacros(item, [macro], { depth: 'shallow' }))
|
|
131
|
+
if (namedItems.some((item, i) => item !== tupleNode.items![i])) {
|
|
132
|
+
return { ...tupleNode, items: namedItems }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return named
|
|
136
|
+
}
|
|
137
|
+
|
|
103
138
|
/**
|
|
104
139
|
* Factory function that creates schema and operation converters for a given OpenAPI context.
|
|
105
140
|
*
|
|
@@ -144,7 +179,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
144
179
|
if (refPath && !resolvingRefs.has(refPath)) {
|
|
145
180
|
if (!resolvedRefCache.has(refPath)) {
|
|
146
181
|
try {
|
|
147
|
-
const referenced = dialect.resolveRef<SchemaObject>(document, refPath)
|
|
182
|
+
const referenced = dialect.schema.resolveRef<SchemaObject>(document, refPath)
|
|
148
183
|
if (referenced) {
|
|
149
184
|
resolvingRefs.add(refPath)
|
|
150
185
|
resolvedSchema = parseSchema({ schema: referenced }, rawOptions)
|
|
@@ -205,13 +240,13 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
205
240
|
}> = []
|
|
206
241
|
const allOfMembers: Array<ast.SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
|
|
207
242
|
.filter((item) => {
|
|
208
|
-
if (!dialect.isReference(item) || !name) return true
|
|
209
|
-
const deref = dialect.resolveRef<SchemaObject>(document, item.$ref)
|
|
210
|
-
if (!deref || !dialect.isDiscriminator(deref)) return true
|
|
243
|
+
if (!dialect.schema.isReference(item) || !name) return true
|
|
244
|
+
const deref = dialect.schema.resolveRef<SchemaObject>(document, item.$ref)
|
|
245
|
+
if (!deref || !dialect.schema.isDiscriminator(deref)) return true
|
|
211
246
|
const parentUnion = deref.oneOf ?? deref.anyOf
|
|
212
247
|
if (!parentUnion) return true
|
|
213
248
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`
|
|
214
|
-
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
249
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.schema.isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
215
250
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
216
251
|
if (inOneOf || inMapping) {
|
|
217
252
|
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef)
|
|
@@ -235,9 +270,9 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
235
270
|
|
|
236
271
|
if (missingRequired.length) {
|
|
237
272
|
const resolvedMembers = (schema.allOf as Array<SchemaObject | ReferenceObject>).flatMap((item) => {
|
|
238
|
-
if (!dialect.isReference(item)) return [item as SchemaObject]
|
|
239
|
-
const deref = dialect.resolveRef<SchemaObject>(document, item.$ref)
|
|
240
|
-
return deref && !dialect.isReference(deref) ? [deref] : []
|
|
273
|
+
if (!dialect.schema.isReference(item)) return [item as SchemaObject]
|
|
274
|
+
const deref = dialect.schema.resolveRef<SchemaObject>(document, item.$ref)
|
|
275
|
+
return deref && !dialect.schema.isReference(deref) ? [deref] : []
|
|
241
276
|
})
|
|
242
277
|
|
|
243
278
|
for (const key of missingRequired) {
|
|
@@ -276,7 +311,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
276
311
|
|
|
277
312
|
return ast.factory.createSchema({
|
|
278
313
|
type: 'intersection',
|
|
279
|
-
members: [...
|
|
314
|
+
members: [...mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
280
315
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
281
316
|
})
|
|
282
317
|
}
|
|
@@ -304,10 +339,10 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
304
339
|
const strategy: 'one' | 'any' = schema.oneOf ? 'one' : 'any'
|
|
305
340
|
const unionBase = {
|
|
306
341
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
307
|
-
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
342
|
+
discriminatorPropertyName: dialect.schema.isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
308
343
|
strategy,
|
|
309
344
|
}
|
|
310
|
-
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : undefined
|
|
345
|
+
const discriminator = dialect.schema.isDiscriminator(schema) ? schema.discriminator : undefined
|
|
311
346
|
const sharedPropertiesNode = schema.properties
|
|
312
347
|
? (() => {
|
|
313
348
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
@@ -320,7 +355,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
320
355
|
|
|
321
356
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
322
357
|
const members = unionMembers.map((s) => {
|
|
323
|
-
const ref = dialect.isReference(s) ? s.$ref : undefined
|
|
358
|
+
const ref = dialect.schema.isReference(s) ? s.$ref : undefined
|
|
324
359
|
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref)
|
|
325
360
|
const memberNode = parseSchema({ schema: s as SchemaObject, name }, rawOptions)
|
|
326
361
|
|
|
@@ -330,10 +365,8 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
330
365
|
|
|
331
366
|
const narrowedDiscriminatorNode = sharedPropertiesNode
|
|
332
367
|
? pickDiscriminatorPropertyNode(
|
|
333
|
-
ast.
|
|
334
|
-
|
|
335
|
-
propertyName: discriminator.propertyName,
|
|
336
|
-
values: [discriminatorValue],
|
|
368
|
+
ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({ propertyName: discriminator.propertyName, values: [discriminatorValue] })], {
|
|
369
|
+
depth: 'shallow',
|
|
337
370
|
}),
|
|
338
371
|
discriminator.propertyName,
|
|
339
372
|
)
|
|
@@ -369,11 +402,13 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
369
402
|
})
|
|
370
403
|
}
|
|
371
404
|
|
|
372
|
-
|
|
405
|
+
const unionNode = ast.factory.createSchema({
|
|
373
406
|
type: 'union',
|
|
374
407
|
...unionBase,
|
|
375
|
-
members:
|
|
408
|
+
members: unionMembers.map((s) => parseSchema({ schema: s as SchemaObject, name }, rawOptions)),
|
|
376
409
|
})
|
|
410
|
+
|
|
411
|
+
return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: 'shallow' })
|
|
377
412
|
}
|
|
378
413
|
|
|
379
414
|
/**
|
|
@@ -383,15 +418,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
383
418
|
const constValue = schema.const
|
|
384
419
|
|
|
385
420
|
if (constValue === null) {
|
|
386
|
-
return
|
|
387
|
-
type: 'null',
|
|
388
|
-
primitive: 'null',
|
|
389
|
-
name,
|
|
390
|
-
title: schema.title,
|
|
391
|
-
description: schema.description,
|
|
392
|
-
deprecated: schema.deprecated,
|
|
393
|
-
format: schema.format,
|
|
394
|
-
})
|
|
421
|
+
return createNullSchema(schema, name)
|
|
395
422
|
}
|
|
396
423
|
|
|
397
424
|
const constPrimitive = getPrimitiveType(typeof constValue === 'number' ? 'number' : typeof constValue === 'boolean' ? 'boolean' : 'string')
|
|
@@ -510,15 +537,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
510
537
|
// render as `never` (plugin-ts) / invalid `z.enum([])` (plugin-zod). Mirror the `const: null`
|
|
511
538
|
// branch so it renders as a clean `null` (not `z.null().nullable()`).
|
|
512
539
|
if (nullInEnum && filteredValues.length === 0) {
|
|
513
|
-
return
|
|
514
|
-
type: 'null',
|
|
515
|
-
primitive: 'null',
|
|
516
|
-
name,
|
|
517
|
-
title: schema.title,
|
|
518
|
-
description: schema.description,
|
|
519
|
-
deprecated: schema.deprecated,
|
|
520
|
-
format: schema.format,
|
|
521
|
-
})
|
|
540
|
+
return createNullSchema(schema, name)
|
|
522
541
|
}
|
|
523
542
|
|
|
524
543
|
const enumNullable = nullable || nullInEnum || undefined
|
|
@@ -581,30 +600,23 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
581
600
|
? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
582
601
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
|
|
583
602
|
const resolvedPropSchema = propSchema as SchemaObject
|
|
584
|
-
const propNullable = dialect.isNullable(resolvedPropSchema)
|
|
603
|
+
const propNullable = dialect.schema.isNullable(resolvedPropSchema)
|
|
585
604
|
|
|
586
605
|
const resolvedChildName = childName(name, propName)
|
|
587
606
|
const propNode = parseSchema({ schema: resolvedPropSchema, name: resolvedChildName }, rawOptions)
|
|
588
|
-
const schemaNode = (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
})()
|
|
599
|
-
|
|
600
|
-
return ast.factory.createProperty({
|
|
601
|
-
name: propName,
|
|
602
|
-
schema: {
|
|
603
|
-
...schemaNode,
|
|
604
|
-
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
607
|
+
const schemaNode = nameEnums(propNode, { parentName: name, propName, enumSuffix: options.enumSuffix })
|
|
608
|
+
|
|
609
|
+
return ast.factory.createProperty(
|
|
610
|
+
{
|
|
611
|
+
name: propName,
|
|
612
|
+
schema: {
|
|
613
|
+
...schemaNode,
|
|
614
|
+
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
615
|
+
},
|
|
616
|
+
required,
|
|
605
617
|
},
|
|
606
|
-
|
|
607
|
-
|
|
618
|
+
dialect,
|
|
619
|
+
)
|
|
608
620
|
})
|
|
609
621
|
: []
|
|
610
622
|
|
|
@@ -645,16 +657,11 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
645
657
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
646
658
|
})
|
|
647
659
|
|
|
648
|
-
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
660
|
+
if (dialect.schema.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
649
661
|
const discPropName = schema.discriminator.propertyName
|
|
650
662
|
const values = Object.keys(schema.discriminator.mapping)
|
|
651
663
|
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : undefined
|
|
652
|
-
return ast.
|
|
653
|
-
node: objectNode,
|
|
654
|
-
propertyName: discPropName,
|
|
655
|
-
values,
|
|
656
|
-
enumName,
|
|
657
|
-
})
|
|
664
|
+
return ast.applyMacros(objectNode, [macroDiscriminatorEnum({ propertyName: discPropName, values, enumName })], { depth: 'shallow' })
|
|
658
665
|
}
|
|
659
666
|
|
|
660
667
|
return objectNode
|
|
@@ -792,14 +799,14 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
792
799
|
* match/convert/fall-through contract.
|
|
793
800
|
*/
|
|
794
801
|
const schemaRules: Array<SchemaRule> = [
|
|
795
|
-
{ name: 'ref', match: ({ schema }) => dialect.isReference(schema), convert: convertRef },
|
|
802
|
+
{ name: 'ref', match: ({ schema }) => dialect.schema.isReference(schema), convert: convertRef },
|
|
796
803
|
{ name: 'allOf', match: ({ schema }) => !!schema.allOf?.length, convert: convertAllOf },
|
|
797
804
|
{ name: 'union', match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length), convert: convertUnion },
|
|
798
805
|
{ name: 'const', match: ({ schema }) => 'const' in schema && schema.const !== undefined, convert: convertConst },
|
|
799
806
|
{ name: 'format', match: ({ schema }) => !!schema.format, convert: convertFormat },
|
|
800
807
|
{
|
|
801
808
|
name: 'blob',
|
|
802
|
-
match: ({ schema }) => dialect.isBinary(schema),
|
|
809
|
+
match: ({ schema }) => dialect.schema.isBinary(schema),
|
|
803
810
|
convert: convertBlob,
|
|
804
811
|
},
|
|
805
812
|
{ name: 'multi-type', match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1, convert: convertMultiType },
|
|
@@ -845,7 +852,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
845
852
|
return parseSchema({ schema: flattenedSchema, name }, rawOptions)
|
|
846
853
|
}
|
|
847
854
|
|
|
848
|
-
const nullable = dialect.isNullable(schema) || undefined
|
|
855
|
+
const nullable = dialect.schema.isNullable(schema) || undefined
|
|
849
856
|
const defaultValue = schema.default === null && nullable ? undefined : schema.default
|
|
850
857
|
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
|
|
851
858
|
|
|
@@ -887,15 +894,18 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
887
894
|
? parseSchema({ schema: param['schema'] as SchemaObject, name: schemaName }, options)
|
|
888
895
|
: ast.factory.createSchema({ type: typeOptionMap.get(options.unknownType)! })
|
|
889
896
|
|
|
890
|
-
return ast.factory.createParameter(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
897
|
+
return ast.factory.createParameter(
|
|
898
|
+
{
|
|
899
|
+
name: paramName,
|
|
900
|
+
in: param['in'] as ast.ParameterLocation,
|
|
901
|
+
schema: {
|
|
902
|
+
...schema,
|
|
903
|
+
description: (param['description'] as string | undefined) ?? schema.description,
|
|
904
|
+
},
|
|
905
|
+
required,
|
|
896
906
|
},
|
|
897
|
-
|
|
898
|
-
|
|
907
|
+
dialect,
|
|
908
|
+
)
|
|
899
909
|
}
|
|
900
910
|
|
|
901
911
|
/**
|
|
@@ -943,7 +953,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
943
953
|
const keys: Array<string> = []
|
|
944
954
|
for (const key in schema.properties) {
|
|
945
955
|
const prop = schema.properties[key]
|
|
946
|
-
if (prop && !dialect.isReference(prop) && (prop as Record<string, unknown>)[flag]) {
|
|
956
|
+
if (prop && !dialect.schema.isReference(prop) && (prop as Record<string, unknown>)[flag]) {
|
|
947
957
|
keys.push(key)
|
|
948
958
|
}
|
|
949
959
|
}
|
|
@@ -972,11 +982,11 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
972
982
|
const schema = getRequestSchema(document, operation, { contentType: ct })
|
|
973
983
|
if (!schema) return []
|
|
974
984
|
return [
|
|
975
|
-
{
|
|
985
|
+
ast.factory.createContent({
|
|
976
986
|
contentType: ct,
|
|
977
|
-
schema:
|
|
987
|
+
schema: dialect.schema.optionality(parseSchema({ schema, name: requestBodyName }, options), requestBodyMeta.required),
|
|
978
988
|
keysToOmit: collectPropertyKeysByFlag(schema, 'readOnly'),
|
|
979
|
-
},
|
|
989
|
+
}),
|
|
980
990
|
]
|
|
981
991
|
})
|
|
982
992
|
|
|
@@ -1010,23 +1020,28 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
1010
1020
|
// Build one entry per declared response content type so plugins can union the variants.
|
|
1011
1021
|
// When a global contentType is configured, restrict to that single type (mirrors requestBody).
|
|
1012
1022
|
const responseContentTypes = ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)
|
|
1013
|
-
const content = responseContentTypes.map((contentType) => ({ contentType, ...parseEntrySchema(contentType) }))
|
|
1023
|
+
const content = responseContentTypes.map((contentType) => ast.factory.createContent({ contentType, ...parseEntrySchema(contentType) }))
|
|
1014
1024
|
|
|
1015
1025
|
// Body-less responses keep a single fallback entry so the response still resolves to a
|
|
1016
1026
|
// (void/any) schema, matching how `requestBody` only carries schemas inside `content`.
|
|
1017
1027
|
if (content.length === 0) {
|
|
1018
|
-
content.push(
|
|
1028
|
+
content.push(
|
|
1029
|
+
ast.factory.createContent({
|
|
1030
|
+
contentType: getRequestContentType({ document, operation }) || 'application/json',
|
|
1031
|
+
...parseEntrySchema(ctx.contentType),
|
|
1032
|
+
}),
|
|
1033
|
+
)
|
|
1019
1034
|
}
|
|
1020
1035
|
|
|
1021
1036
|
return ast.factory.createResponse({
|
|
1022
|
-
statusCode: statusCode as
|
|
1037
|
+
statusCode: statusCode as StatusCode,
|
|
1023
1038
|
description,
|
|
1024
1039
|
content,
|
|
1025
1040
|
})
|
|
1026
1041
|
})
|
|
1027
1042
|
|
|
1028
1043
|
const pathItem = document.paths?.[operation.path]
|
|
1029
|
-
const pathItemDoc = pathItem && !dialect.isReference(pathItem) ? (pathItem as { summary?: unknown; description?: unknown }) : undefined
|
|
1044
|
+
const pathItemDoc = pathItem && !dialect.schema.isReference(pathItem) ? (pathItem as { summary?: unknown; description?: unknown }) : undefined
|
|
1030
1045
|
const pickDoc = (key: 'summary' | 'description'): string | undefined => {
|
|
1031
1046
|
const own = operation.schema[key]
|
|
1032
1047
|
if (typeof own === 'string') return own
|
package/src/stream.ts
CHANGED
|
@@ -17,52 +17,6 @@ export type PreScanResult = {
|
|
|
17
17
|
dedupePlan: ast.DedupePlan | null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Builds the deduplication plan from the already-parsed top-level schema nodes plus a
|
|
22
|
-
* single extra parse pass over operations (so duplicates in request/response bodies are seen).
|
|
23
|
-
*
|
|
24
|
-
* Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
25
|
-
* rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
26
|
-
* name (collision-resolved against existing component names). Shapes without a name stay inline.
|
|
27
|
-
*/
|
|
28
|
-
function createDedupePlan({
|
|
29
|
-
schemaNodes,
|
|
30
|
-
operationNodes,
|
|
31
|
-
schemaNames,
|
|
32
|
-
circularNames,
|
|
33
|
-
}: {
|
|
34
|
-
schemaNodes: Array<ast.SchemaNode>
|
|
35
|
-
operationNodes: Array<ast.OperationNode>
|
|
36
|
-
schemaNames: Array<string>
|
|
37
|
-
circularNames: Array<string>
|
|
38
|
-
}): ast.DedupePlan {
|
|
39
|
-
const circularSchemas = new Set(circularNames)
|
|
40
|
-
const usedNames = new Set(schemaNames)
|
|
41
|
-
|
|
42
|
-
return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
43
|
-
isCandidate: (node) => {
|
|
44
|
-
if (node.type === 'enum') return true
|
|
45
|
-
if (node.type !== 'object') return false
|
|
46
|
-
// Skip object shapes that are part of a circular chain, hoisting them would break the cycle.
|
|
47
|
-
if (node.name && circularSchemas.has(node.name)) return false
|
|
48
|
-
return !containsCircularRef(node, { circularSchemas })
|
|
49
|
-
},
|
|
50
|
-
nameFor: (node) => {
|
|
51
|
-
const base = node.name
|
|
52
|
-
if (!base) return null
|
|
53
|
-
|
|
54
|
-
let name = base
|
|
55
|
-
let counter = 2
|
|
56
|
-
while (usedNames.has(name)) {
|
|
57
|
-
name = `${base}${counter++}`
|
|
58
|
-
}
|
|
59
|
-
usedNames.add(name)
|
|
60
|
-
return name
|
|
61
|
-
},
|
|
62
|
-
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`,
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
20
|
/**
|
|
67
21
|
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
68
22
|
* interpolating any `serverVariables` into the URL template.
|
|
@@ -163,7 +117,34 @@ export function preScan({
|
|
|
163
117
|
if (operationNode) operationNodes.push(operationNode)
|
|
164
118
|
}
|
|
165
119
|
|
|
166
|
-
|
|
120
|
+
// Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
121
|
+
// rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
122
|
+
// name (collision-resolved against existing component names). Shapes without a name stay inline.
|
|
123
|
+
const circularSchemas = new Set(circularNames)
|
|
124
|
+
const usedNames = new Set(Object.keys(schemas))
|
|
125
|
+
|
|
126
|
+
dedupePlan = ast.buildDedupePlan([...allNodes, ...operationNodes], {
|
|
127
|
+
isCandidate: (node) => {
|
|
128
|
+
if (node.type === 'enum') return true
|
|
129
|
+
if (node.type !== 'object') return false
|
|
130
|
+
// Skip object shapes that are part of a circular chain, hoisting them would break the cycle.
|
|
131
|
+
if (node.name && circularSchemas.has(node.name)) return false
|
|
132
|
+
return !containsCircularRef(node, { circularSchemas })
|
|
133
|
+
},
|
|
134
|
+
nameFor: (node) => {
|
|
135
|
+
const base = node.name
|
|
136
|
+
if (!base) return null
|
|
137
|
+
|
|
138
|
+
let name = base
|
|
139
|
+
let counter = 2
|
|
140
|
+
while (usedNames.has(name)) {
|
|
141
|
+
name = `${base}${counter++}`
|
|
142
|
+
}
|
|
143
|
+
usedNames.add(name)
|
|
144
|
+
return name
|
|
145
|
+
},
|
|
146
|
+
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`,
|
|
147
|
+
})
|
|
167
148
|
|
|
168
149
|
for (const definition of dedupePlan.hoisted) {
|
|
169
150
|
if (definition.type === 'enum' && definition.name) enumNames.push(definition.name)
|