@kubb/adapter-oas 5.0.0-beta.61 → 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 +88 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +88 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/dialect.ts +28 -6
- package/src/parser.ts +69 -57
- 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
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getSchemaType,
|
|
21
21
|
} from './resolvers.ts'
|
|
22
22
|
import type { ContentType, Document, Operation, ReferenceObject, SchemaObject } from './types.ts'
|
|
23
|
+
import type { StatusCode } from '@kubb/ast'
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Parser context holding the raw OpenAPI document and optional content-type override.
|
|
@@ -101,6 +102,22 @@ function normalizeArrayEnum(schema: SchemaObject): SchemaObject {
|
|
|
101
102
|
return { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject
|
|
102
103
|
}
|
|
103
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
|
+
|
|
104
121
|
/**
|
|
105
122
|
* Names the inline enums on a property's schema, and on each item when the property is a tuple, from
|
|
106
123
|
* the parent and property name. Wraps `macroEnumName` at the property construction site.
|
|
@@ -162,7 +179,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
162
179
|
if (refPath && !resolvingRefs.has(refPath)) {
|
|
163
180
|
if (!resolvedRefCache.has(refPath)) {
|
|
164
181
|
try {
|
|
165
|
-
const referenced = dialect.resolveRef<SchemaObject>(document, refPath)
|
|
182
|
+
const referenced = dialect.schema.resolveRef<SchemaObject>(document, refPath)
|
|
166
183
|
if (referenced) {
|
|
167
184
|
resolvingRefs.add(refPath)
|
|
168
185
|
resolvedSchema = parseSchema({ schema: referenced }, rawOptions)
|
|
@@ -223,13 +240,13 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
223
240
|
}> = []
|
|
224
241
|
const allOfMembers: Array<ast.SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
|
|
225
242
|
.filter((item) => {
|
|
226
|
-
if (!dialect.isReference(item) || !name) return true
|
|
227
|
-
const deref = dialect.resolveRef<SchemaObject>(document, item.$ref)
|
|
228
|
-
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
|
|
229
246
|
const parentUnion = deref.oneOf ?? deref.anyOf
|
|
230
247
|
if (!parentUnion) return true
|
|
231
248
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`
|
|
232
|
-
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
249
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.schema.isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
233
250
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
234
251
|
if (inOneOf || inMapping) {
|
|
235
252
|
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef)
|
|
@@ -253,9 +270,9 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
253
270
|
|
|
254
271
|
if (missingRequired.length) {
|
|
255
272
|
const resolvedMembers = (schema.allOf as Array<SchemaObject | ReferenceObject>).flatMap((item) => {
|
|
256
|
-
if (!dialect.isReference(item)) return [item as SchemaObject]
|
|
257
|
-
const deref = dialect.resolveRef<SchemaObject>(document, item.$ref)
|
|
258
|
-
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] : []
|
|
259
276
|
})
|
|
260
277
|
|
|
261
278
|
for (const key of missingRequired) {
|
|
@@ -322,10 +339,10 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
322
339
|
const strategy: 'one' | 'any' = schema.oneOf ? 'one' : 'any'
|
|
323
340
|
const unionBase = {
|
|
324
341
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
325
|
-
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
342
|
+
discriminatorPropertyName: dialect.schema.isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
326
343
|
strategy,
|
|
327
344
|
}
|
|
328
|
-
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : undefined
|
|
345
|
+
const discriminator = dialect.schema.isDiscriminator(schema) ? schema.discriminator : undefined
|
|
329
346
|
const sharedPropertiesNode = schema.properties
|
|
330
347
|
? (() => {
|
|
331
348
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
@@ -338,7 +355,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
338
355
|
|
|
339
356
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
340
357
|
const members = unionMembers.map((s) => {
|
|
341
|
-
const ref = dialect.isReference(s) ? s.$ref : undefined
|
|
358
|
+
const ref = dialect.schema.isReference(s) ? s.$ref : undefined
|
|
342
359
|
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref)
|
|
343
360
|
const memberNode = parseSchema({ schema: s as SchemaObject, name }, rawOptions)
|
|
344
361
|
|
|
@@ -401,15 +418,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
401
418
|
const constValue = schema.const
|
|
402
419
|
|
|
403
420
|
if (constValue === null) {
|
|
404
|
-
return
|
|
405
|
-
type: 'null',
|
|
406
|
-
primitive: 'null',
|
|
407
|
-
name,
|
|
408
|
-
title: schema.title,
|
|
409
|
-
description: schema.description,
|
|
410
|
-
deprecated: schema.deprecated,
|
|
411
|
-
format: schema.format,
|
|
412
|
-
})
|
|
421
|
+
return createNullSchema(schema, name)
|
|
413
422
|
}
|
|
414
423
|
|
|
415
424
|
const constPrimitive = getPrimitiveType(typeof constValue === 'number' ? 'number' : typeof constValue === 'boolean' ? 'boolean' : 'string')
|
|
@@ -528,15 +537,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
528
537
|
// render as `never` (plugin-ts) / invalid `z.enum([])` (plugin-zod). Mirror the `const: null`
|
|
529
538
|
// branch so it renders as a clean `null` (not `z.null().nullable()`).
|
|
530
539
|
if (nullInEnum && filteredValues.length === 0) {
|
|
531
|
-
return
|
|
532
|
-
type: 'null',
|
|
533
|
-
primitive: 'null',
|
|
534
|
-
name,
|
|
535
|
-
title: schema.title,
|
|
536
|
-
description: schema.description,
|
|
537
|
-
deprecated: schema.deprecated,
|
|
538
|
-
format: schema.format,
|
|
539
|
-
})
|
|
540
|
+
return createNullSchema(schema, name)
|
|
540
541
|
}
|
|
541
542
|
|
|
542
543
|
const enumNullable = nullable || nullInEnum || undefined
|
|
@@ -599,20 +600,23 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
599
600
|
? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
600
601
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
|
|
601
602
|
const resolvedPropSchema = propSchema as SchemaObject
|
|
602
|
-
const propNullable = dialect.isNullable(resolvedPropSchema)
|
|
603
|
+
const propNullable = dialect.schema.isNullable(resolvedPropSchema)
|
|
603
604
|
|
|
604
605
|
const resolvedChildName = childName(name, propName)
|
|
605
606
|
const propNode = parseSchema({ schema: resolvedPropSchema, name: resolvedChildName }, rawOptions)
|
|
606
607
|
const schemaNode = nameEnums(propNode, { parentName: name, propName, enumSuffix: options.enumSuffix })
|
|
607
608
|
|
|
608
|
-
return ast.factory.createProperty(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
609
|
+
return ast.factory.createProperty(
|
|
610
|
+
{
|
|
611
|
+
name: propName,
|
|
612
|
+
schema: {
|
|
613
|
+
...schemaNode,
|
|
614
|
+
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
615
|
+
},
|
|
616
|
+
required,
|
|
613
617
|
},
|
|
614
|
-
|
|
615
|
-
|
|
618
|
+
dialect,
|
|
619
|
+
)
|
|
616
620
|
})
|
|
617
621
|
: []
|
|
618
622
|
|
|
@@ -653,7 +657,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
653
657
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
654
658
|
})
|
|
655
659
|
|
|
656
|
-
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
660
|
+
if (dialect.schema.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
657
661
|
const discPropName = schema.discriminator.propertyName
|
|
658
662
|
const values = Object.keys(schema.discriminator.mapping)
|
|
659
663
|
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : undefined
|
|
@@ -795,14 +799,14 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
795
799
|
* match/convert/fall-through contract.
|
|
796
800
|
*/
|
|
797
801
|
const schemaRules: Array<SchemaRule> = [
|
|
798
|
-
{ name: 'ref', match: ({ schema }) => dialect.isReference(schema), convert: convertRef },
|
|
802
|
+
{ name: 'ref', match: ({ schema }) => dialect.schema.isReference(schema), convert: convertRef },
|
|
799
803
|
{ name: 'allOf', match: ({ schema }) => !!schema.allOf?.length, convert: convertAllOf },
|
|
800
804
|
{ name: 'union', match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length), convert: convertUnion },
|
|
801
805
|
{ name: 'const', match: ({ schema }) => 'const' in schema && schema.const !== undefined, convert: convertConst },
|
|
802
806
|
{ name: 'format', match: ({ schema }) => !!schema.format, convert: convertFormat },
|
|
803
807
|
{
|
|
804
808
|
name: 'blob',
|
|
805
|
-
match: ({ schema }) => dialect.isBinary(schema),
|
|
809
|
+
match: ({ schema }) => dialect.schema.isBinary(schema),
|
|
806
810
|
convert: convertBlob,
|
|
807
811
|
},
|
|
808
812
|
{ name: 'multi-type', match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1, convert: convertMultiType },
|
|
@@ -848,7 +852,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
848
852
|
return parseSchema({ schema: flattenedSchema, name }, rawOptions)
|
|
849
853
|
}
|
|
850
854
|
|
|
851
|
-
const nullable = dialect.isNullable(schema) || undefined
|
|
855
|
+
const nullable = dialect.schema.isNullable(schema) || undefined
|
|
852
856
|
const defaultValue = schema.default === null && nullable ? undefined : schema.default
|
|
853
857
|
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
|
|
854
858
|
|
|
@@ -890,15 +894,18 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
890
894
|
? parseSchema({ schema: param['schema'] as SchemaObject, name: schemaName }, options)
|
|
891
895
|
: ast.factory.createSchema({ type: typeOptionMap.get(options.unknownType)! })
|
|
892
896
|
|
|
893
|
-
return ast.factory.createParameter(
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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,
|
|
899
906
|
},
|
|
900
|
-
|
|
901
|
-
|
|
907
|
+
dialect,
|
|
908
|
+
)
|
|
902
909
|
}
|
|
903
910
|
|
|
904
911
|
/**
|
|
@@ -946,7 +953,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
946
953
|
const keys: Array<string> = []
|
|
947
954
|
for (const key in schema.properties) {
|
|
948
955
|
const prop = schema.properties[key]
|
|
949
|
-
if (prop && !dialect.isReference(prop) && (prop as Record<string, unknown>)[flag]) {
|
|
956
|
+
if (prop && !dialect.schema.isReference(prop) && (prop as Record<string, unknown>)[flag]) {
|
|
950
957
|
keys.push(key)
|
|
951
958
|
}
|
|
952
959
|
}
|
|
@@ -975,11 +982,11 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
975
982
|
const schema = getRequestSchema(document, operation, { contentType: ct })
|
|
976
983
|
if (!schema) return []
|
|
977
984
|
return [
|
|
978
|
-
{
|
|
985
|
+
ast.factory.createContent({
|
|
979
986
|
contentType: ct,
|
|
980
|
-
schema:
|
|
987
|
+
schema: dialect.schema.optionality(parseSchema({ schema, name: requestBodyName }, options), requestBodyMeta.required),
|
|
981
988
|
keysToOmit: collectPropertyKeysByFlag(schema, 'readOnly'),
|
|
982
|
-
},
|
|
989
|
+
}),
|
|
983
990
|
]
|
|
984
991
|
})
|
|
985
992
|
|
|
@@ -1013,23 +1020,28 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
1013
1020
|
// Build one entry per declared response content type so plugins can union the variants.
|
|
1014
1021
|
// When a global contentType is configured, restrict to that single type (mirrors requestBody).
|
|
1015
1022
|
const responseContentTypes = ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)
|
|
1016
|
-
const content = responseContentTypes.map((contentType) => ({ contentType, ...parseEntrySchema(contentType) }))
|
|
1023
|
+
const content = responseContentTypes.map((contentType) => ast.factory.createContent({ contentType, ...parseEntrySchema(contentType) }))
|
|
1017
1024
|
|
|
1018
1025
|
// Body-less responses keep a single fallback entry so the response still resolves to a
|
|
1019
1026
|
// (void/any) schema, matching how `requestBody` only carries schemas inside `content`.
|
|
1020
1027
|
if (content.length === 0) {
|
|
1021
|
-
content.push(
|
|
1028
|
+
content.push(
|
|
1029
|
+
ast.factory.createContent({
|
|
1030
|
+
contentType: getRequestContentType({ document, operation }) || 'application/json',
|
|
1031
|
+
...parseEntrySchema(ctx.contentType),
|
|
1032
|
+
}),
|
|
1033
|
+
)
|
|
1022
1034
|
}
|
|
1023
1035
|
|
|
1024
1036
|
return ast.factory.createResponse({
|
|
1025
|
-
statusCode: statusCode as
|
|
1037
|
+
statusCode: statusCode as StatusCode,
|
|
1026
1038
|
description,
|
|
1027
1039
|
content,
|
|
1028
1040
|
})
|
|
1029
1041
|
})
|
|
1030
1042
|
|
|
1031
1043
|
const pathItem = document.paths?.[operation.path]
|
|
1032
|
-
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
|
|
1033
1045
|
const pickDoc = (key: 'summary' | 'description'): string | undefined => {
|
|
1034
1046
|
const own = operation.schema[key]
|
|
1035
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)
|