@kubb/adapter-oas 5.0.0-alpha.42 → 5.0.0-alpha.44
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 +161 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +332 -13
- package/dist/index.js +159 -60
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/constants.ts +12 -0
- package/src/factory.ts +8 -12
- package/src/guards.ts +1 -1
- package/src/index.ts +2 -2
- package/src/parser.ts +68 -54
- package/src/refs.ts +4 -2
- package/src/resolvers.ts +48 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/adapter-oas",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.44",
|
|
4
4
|
"description": "OpenAPI / Swagger adapter for Kubb converts OAS input into a @kubb/ast RootNode.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openapi",
|
|
@@ -37,18 +37,14 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@redocly/openapi-core": "^2.28.1",
|
|
40
|
-
"@stoplight/yaml": "^4.3.0",
|
|
41
|
-
"fflate": "^0.8.2",
|
|
42
|
-
"jsonpointer": "^5.0.1",
|
|
43
40
|
"oas": "^31.1.2",
|
|
44
41
|
"oas-normalize": "^16.0.4",
|
|
45
|
-
"openapi-types": "^12.1.3",
|
|
46
|
-
"remeda": "^2.33.7",
|
|
47
42
|
"swagger2openapi": "^7.0.8",
|
|
48
|
-
"@kubb/core": "5.0.0-alpha.
|
|
43
|
+
"@kubb/core": "5.0.0-alpha.44"
|
|
49
44
|
},
|
|
50
45
|
"devDependencies": {
|
|
51
46
|
"@types/swagger2openapi": "^7.0.4",
|
|
47
|
+
"openapi-types": "^12.1.3",
|
|
52
48
|
"@internals/utils": "0.0.0"
|
|
53
49
|
},
|
|
54
50
|
"engines": {
|
|
@@ -60,6 +56,9 @@
|
|
|
60
56
|
},
|
|
61
57
|
"main": "./dist/index.cjs",
|
|
62
58
|
"module": "./dist/index.js",
|
|
59
|
+
"inlinedDependencies": {
|
|
60
|
+
"openapi-types": "12.1.3"
|
|
61
|
+
},
|
|
63
62
|
"scripts": {
|
|
64
63
|
"build": "tsdown",
|
|
65
64
|
"clean": "npx rimraf ./dist",
|
package/src/constants.ts
CHANGED
|
@@ -19,6 +19,18 @@ export const DEFAULT_PARSER_OPTIONS = {
|
|
|
19
19
|
enumSuffix: 'enum',
|
|
20
20
|
} as const satisfies ast.ParserOptions
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
24
|
+
*
|
|
25
|
+
* Used when building or parsing `$ref` strings.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const SCHEMA_REF_PREFIX = '#/components/schemas/' as const
|
|
33
|
+
|
|
22
34
|
/**
|
|
23
35
|
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
24
36
|
*/
|
package/src/factory.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import { URLPath } from '@internals/utils'
|
|
2
|
+
import { mergeDeep, URLPath } from '@internals/utils'
|
|
3
3
|
import type { AdapterSource } from '@kubb/core'
|
|
4
4
|
import { bundle, loadConfig } from '@redocly/openapi-core'
|
|
5
|
-
import yaml from '@stoplight/yaml'
|
|
6
5
|
import OASNormalize from 'oas-normalize'
|
|
7
|
-
import { mergeDeep } from 'remeda'
|
|
8
6
|
import swagger2openapi from 'swagger2openapi'
|
|
9
7
|
import { MERGE_DEFAULT_TITLE, MERGE_DEFAULT_VERSION, MERGE_OPENAPI_VERSION } from './constants.ts'
|
|
10
8
|
import { isOpenApiV2Document } from './guards.ts'
|
|
@@ -60,7 +58,7 @@ export async function parseDocument(pathOrApi: string | Document, { canBundle =
|
|
|
60
58
|
/**
|
|
61
59
|
* Deep-merges multiple OpenAPI documents into a single `Document`.
|
|
62
60
|
*
|
|
63
|
-
* Each document is parsed independently then recursively merged
|
|
61
|
+
* Each document is parsed independently then recursively merged via `mergeDeep` from `@internals/utils`.
|
|
64
62
|
* Throws when the input array is empty.
|
|
65
63
|
*
|
|
66
64
|
* @example
|
|
@@ -85,9 +83,12 @@ export async function mergeDocuments(pathOrApi: Array<string | Document>): Promi
|
|
|
85
83
|
components: { schemas: {} },
|
|
86
84
|
} as Document
|
|
87
85
|
|
|
88
|
-
const merged = documents.reduce(
|
|
86
|
+
const merged = documents.reduce(
|
|
87
|
+
(acc, current) => mergeDeep(acc as Record<string, unknown>, current as Record<string, unknown>),
|
|
88
|
+
seed as Record<string, unknown>,
|
|
89
|
+
)
|
|
89
90
|
|
|
90
|
-
return parseDocument(merged)
|
|
91
|
+
return parseDocument(merged as Document)
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/**
|
|
@@ -110,12 +111,7 @@ export function parseFromConfig(source: AdapterSource): Promise<Document> {
|
|
|
110
111
|
return parseDocument(structuredClone(source.data) as Document)
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
const api: string = yaml.parse(source.data as string)
|
|
115
|
-
return parseDocument(api)
|
|
116
|
-
} catch {
|
|
117
|
-
return parseDocument(source.data as string)
|
|
118
|
-
}
|
|
114
|
+
return parseDocument(source.data as string, { canBundle: false })
|
|
119
115
|
}
|
|
120
116
|
|
|
121
117
|
if (source.type === 'paths') {
|
package/src/guards.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { adapterOas, adapterOasName } from './adapter.ts'
|
|
2
|
+
export type { ParseOptions, ValidateDocumentOptions } from './factory.ts'
|
|
2
3
|
export { mergeDocuments, parseDocument, parseFromConfig, validateDocument } from './factory.ts'
|
|
3
|
-
export { HttpMethods } from './types.ts'
|
|
4
4
|
export type {
|
|
5
5
|
AdapterOas,
|
|
6
6
|
AdapterOasOptions,
|
|
@@ -15,4 +15,4 @@ export type {
|
|
|
15
15
|
ResponseObject,
|
|
16
16
|
SchemaObject,
|
|
17
17
|
} from './types.ts'
|
|
18
|
-
export
|
|
18
|
+
export { HttpMethods } from './types.ts'
|
package/src/parser.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { URLPath } from '@internals/utils'
|
|
2
2
|
import { ast } from '@kubb/core'
|
|
3
3
|
import BaseOas from 'oas'
|
|
4
|
-
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, typeOptionMap } from './constants.ts'
|
|
4
|
+
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, SCHEMA_REF_PREFIX, typeOptionMap } from './constants.ts'
|
|
5
5
|
import { isDiscriminator, isNullable, isReference } from './guards.ts'
|
|
6
6
|
import { resolveRef } from './refs.ts'
|
|
7
7
|
import {
|
|
@@ -148,7 +148,7 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
148
148
|
if (!deref || !isDiscriminator(deref)) return true
|
|
149
149
|
const parentUnion = deref.oneOf ?? deref.anyOf
|
|
150
150
|
if (!parentUnion) return true
|
|
151
|
-
const childRef =
|
|
151
|
+
const childRef = `${SCHEMA_REF_PREFIX}${name}`
|
|
152
152
|
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
153
153
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
154
154
|
if (inOneOf || inMapping) {
|
|
@@ -404,18 +404,24 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
404
404
|
| 'number'
|
|
405
405
|
| 'boolean'
|
|
406
406
|
| 'string'
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
const rawEnumNames = extensionKey ? ((schema as Record<string, unknown>)[extensionKey] as Array<string | number>) : undefined
|
|
408
|
+
const uniqueValues = [...new Set(filteredValues)]
|
|
409
|
+
const seenNames = new Set<string>()
|
|
410
410
|
|
|
411
411
|
return ast.createSchema({
|
|
412
412
|
...enumBase,
|
|
413
413
|
primitive: enumPrimitiveType,
|
|
414
|
-
namedEnumValues:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
414
|
+
namedEnumValues: uniqueValues
|
|
415
|
+
.map((value, index) => ({
|
|
416
|
+
name: String(rawEnumNames?.[index] ?? value),
|
|
417
|
+
value,
|
|
418
|
+
primitive: enumPrimitiveType,
|
|
419
|
+
}))
|
|
420
|
+
.filter((entry) => {
|
|
421
|
+
if (seenNames.has(entry.name)) return false
|
|
422
|
+
seenNames.add(entry.name)
|
|
423
|
+
return true
|
|
424
|
+
}),
|
|
419
425
|
})
|
|
420
426
|
}
|
|
421
427
|
|
|
@@ -691,6 +697,49 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
691
697
|
})
|
|
692
698
|
}
|
|
693
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Reads the inline `requestBody` metadata (description / required / contentType) that OAS exposes
|
|
702
|
+
* outside the schema itself. Returns an empty object when the request body is missing or a `$ref`.
|
|
703
|
+
*/
|
|
704
|
+
function getRequestBodyMeta(operation: Operation): { description?: string; required: boolean; contentType?: string } {
|
|
705
|
+
const body = operation.schema.requestBody
|
|
706
|
+
if (!body || isReference(body)) return { required: false }
|
|
707
|
+
|
|
708
|
+
const inline = body as { description?: string; required?: boolean; content?: Record<string, unknown> }
|
|
709
|
+
return {
|
|
710
|
+
description: inline.description,
|
|
711
|
+
required: inline.required === true,
|
|
712
|
+
contentType: inline.content ? Object.keys(inline.content)[0] : undefined,
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Reads the inline response object (not a `$ref`) and returns its description plus its `content` map.
|
|
718
|
+
*/
|
|
719
|
+
function getResponseMeta(responseObj: unknown): { description?: string; content?: Record<string, unknown> } {
|
|
720
|
+
if (typeof responseObj !== 'object' || responseObj === null || Array.isArray(responseObj)) return {}
|
|
721
|
+
|
|
722
|
+
const inline = responseObj as { description?: string; content?: Record<string, unknown> }
|
|
723
|
+
return { description: inline.description, content: inline.content }
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
|
|
728
|
+
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
729
|
+
*/
|
|
730
|
+
function collectPropertyKeysByFlag(schema: SchemaObject | null, flag: 'readOnly' | 'writeOnly'): string[] | undefined {
|
|
731
|
+
if (!schema?.properties) return undefined
|
|
732
|
+
|
|
733
|
+
const keys: string[] = []
|
|
734
|
+
for (const key in schema.properties) {
|
|
735
|
+
const prop = schema.properties[key]
|
|
736
|
+
if (prop && !isReference(prop) && (prop as Record<string, unknown>)[flag]) {
|
|
737
|
+
keys.push(key)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return keys.length ? keys : undefined
|
|
741
|
+
}
|
|
742
|
+
|
|
694
743
|
/**
|
|
695
744
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
696
745
|
*/
|
|
@@ -701,38 +750,15 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
701
750
|
|
|
702
751
|
const requestBodySchema = getRequestSchema(document, operation, { contentType: ctx.contentType })
|
|
703
752
|
const requestBodySchemaNode = requestBodySchema ? parseSchema({ schema: requestBodySchema }, options) : undefined
|
|
704
|
-
|
|
705
|
-
const requestBodyDescription =
|
|
706
|
-
operation.schema.requestBody && !isReference(operation.schema.requestBody)
|
|
707
|
-
? (operation.schema.requestBody as { description?: string }).description
|
|
708
|
-
: undefined
|
|
709
|
-
|
|
710
|
-
const requestBodyKeysToOmit = requestBodySchema?.properties
|
|
711
|
-
? Object.entries(requestBodySchema.properties)
|
|
712
|
-
.filter(([, prop]) => !isReference(prop) && (prop as { readOnly?: boolean }).readOnly)
|
|
713
|
-
.map(([key]) => key)
|
|
714
|
-
: undefined
|
|
715
|
-
|
|
716
|
-
const requestBodyRequired =
|
|
717
|
-
operation.schema.requestBody && !isReference(operation.schema.requestBody)
|
|
718
|
-
? (operation.schema.requestBody as { required?: boolean }).required === true
|
|
719
|
-
: false
|
|
720
|
-
|
|
721
|
-
const requestBodyContentType = (() => {
|
|
722
|
-
if (!operation.schema.requestBody || isReference(operation.schema.requestBody)) {
|
|
723
|
-
return undefined
|
|
724
|
-
}
|
|
725
|
-
const content = (operation.schema.requestBody as { content?: Record<string, unknown> }).content
|
|
726
|
-
return content ? Object.keys(content)[0] : undefined
|
|
727
|
-
})()
|
|
753
|
+
const requestBodyMeta = getRequestBodyMeta(operation)
|
|
728
754
|
|
|
729
755
|
const requestBody = requestBodySchemaNode
|
|
730
756
|
? {
|
|
731
|
-
description:
|
|
732
|
-
schema: ast.syncOptionality(requestBodySchemaNode,
|
|
733
|
-
keysToOmit:
|
|
734
|
-
required:
|
|
735
|
-
contentType:
|
|
757
|
+
description: requestBodyMeta.description,
|
|
758
|
+
schema: ast.syncOptionality(requestBodySchemaNode, requestBodyMeta.required),
|
|
759
|
+
keysToOmit: collectPropertyKeysByFlag(requestBodySchema, 'readOnly'),
|
|
760
|
+
required: requestBodyMeta.required || undefined,
|
|
761
|
+
contentType: requestBodyMeta.contentType,
|
|
736
762
|
}
|
|
737
763
|
: undefined
|
|
738
764
|
|
|
@@ -745,27 +771,15 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
745
771
|
? parseSchema({ schema: responseSchema }, options)
|
|
746
772
|
: ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType)! })
|
|
747
773
|
|
|
748
|
-
const description
|
|
749
|
-
|
|
750
|
-
const rawContent =
|
|
751
|
-
typeof responseObj === 'object' && responseObj !== null && !Array.isArray(responseObj)
|
|
752
|
-
? (responseObj as { content?: Record<string, unknown> }).content
|
|
753
|
-
: undefined
|
|
754
|
-
|
|
755
|
-
const mediaType = rawContent ? getMediaType(Object.keys(rawContent)[0] ?? '') : getMediaType(operation.contentType ?? '')
|
|
756
|
-
|
|
757
|
-
const keysToOmit = responseSchema?.properties
|
|
758
|
-
? Object.entries(responseSchema.properties)
|
|
759
|
-
.filter(([, prop]) => !isReference(prop) && (prop as { writeOnly?: boolean }).writeOnly)
|
|
760
|
-
.map(([key]) => key)
|
|
761
|
-
: undefined
|
|
774
|
+
const { description, content } = getResponseMeta(responseObj)
|
|
775
|
+
const mediaType = content ? getMediaType(Object.keys(content)[0] ?? '') : getMediaType(operation.contentType ?? '')
|
|
762
776
|
|
|
763
777
|
return ast.createResponse({
|
|
764
778
|
statusCode: statusCode as ast.StatusCode,
|
|
765
779
|
description,
|
|
766
780
|
schema,
|
|
767
781
|
mediaType,
|
|
768
|
-
keysToOmit:
|
|
782
|
+
keysToOmit: collectPropertyKeysByFlag(responseSchema, 'writeOnly'),
|
|
769
783
|
})
|
|
770
784
|
})
|
|
771
785
|
|
package/src/refs.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import jsonpointer from 'jsonpointer'
|
|
2
1
|
import { isReference } from './guards.ts'
|
|
3
2
|
import type { Document } from './types.ts'
|
|
4
3
|
|
|
@@ -24,7 +23,10 @@ export function resolveRef<T = unknown>(document: Document, $ref: string): T | n
|
|
|
24
23
|
} else {
|
|
25
24
|
return null
|
|
26
25
|
}
|
|
27
|
-
const current =
|
|
26
|
+
const current = $ref
|
|
27
|
+
.split('/')
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.reduce((obj: unknown, key: string) => (obj as Record<string, unknown>)?.[key], document as unknown)
|
|
28
30
|
|
|
29
31
|
if (!current) {
|
|
30
32
|
throw new Error(`Could not find a definition for ${origRef}.`)
|
package/src/resolvers.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ast } from '@kubb/core'
|
|
|
3
3
|
import type { ParameterObject, ServerObject } from 'oas/types'
|
|
4
4
|
import { isRef } from 'oas/types'
|
|
5
5
|
import { matchesMimeType } from 'oas/utils'
|
|
6
|
-
import { formatMap, structuralKeys } from './constants.ts'
|
|
6
|
+
import { formatMap, SCHEMA_REF_PREFIX, structuralKeys } from './constants.ts'
|
|
7
7
|
import { isReference } from './guards.ts'
|
|
8
8
|
import { dereferenceWithRef, resolveRef } from './refs.ts'
|
|
9
9
|
import type { ContentType, Document, MediaTypeObject, Operation, ResponseObject, SchemaObject } from './types.ts'
|
|
@@ -128,16 +128,15 @@ function getResponseBody(responseBody: boolean | ResponseObject, contentType?: s
|
|
|
128
128
|
|
|
129
129
|
let availableContentType: string | undefined
|
|
130
130
|
const contentTypes = Object.keys(body.content)
|
|
131
|
-
|
|
132
|
-
if (
|
|
131
|
+
for (const mt of contentTypes) {
|
|
132
|
+
if (matchesMimeType.json(mt)) {
|
|
133
133
|
availableContentType = mt
|
|
134
|
+
break
|
|
134
135
|
}
|
|
135
|
-
}
|
|
136
|
+
}
|
|
136
137
|
|
|
137
138
|
if (!availableContentType) {
|
|
138
|
-
|
|
139
|
-
if (!availableContentType) availableContentType = mt
|
|
140
|
-
})
|
|
139
|
+
availableContentType = contentTypes[0]
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
if (availableContentType) {
|
|
@@ -160,14 +159,13 @@ function getResponseBody(responseBody: boolean | ResponseObject, contentType?: s
|
|
|
160
159
|
*/
|
|
161
160
|
export function getResponseSchema(document: Document, operation: Operation, statusCode: string | number, options: OperationsOptions = {}): SchemaObject {
|
|
162
161
|
if (operation.schema.responses) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
operation.schema.responses![key] = resolveRef<any>(document, $ref)
|
|
162
|
+
const responses = operation.schema.responses
|
|
163
|
+
for (const key in responses) {
|
|
164
|
+
const schema = responses[key]
|
|
165
|
+
if (schema && isReference(schema)) {
|
|
166
|
+
responses[key] = resolveRef<any>(document, schema.$ref)
|
|
169
167
|
}
|
|
170
|
-
}
|
|
168
|
+
}
|
|
171
169
|
}
|
|
172
170
|
|
|
173
171
|
const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType)
|
|
@@ -254,19 +252,30 @@ export type GetSchemasResult = {
|
|
|
254
252
|
* // returned unchanged — contains a $ref
|
|
255
253
|
* ```
|
|
256
254
|
*/
|
|
255
|
+
/**
|
|
256
|
+
* Returns `true` when `fragment` carries any JSON Schema keyword that makes it
|
|
257
|
+
* structurally significant on its own (see `structuralKeys`).
|
|
258
|
+
*
|
|
259
|
+
* A fragment with a structural keyword can't be safely merged into a parent schema.
|
|
260
|
+
*/
|
|
261
|
+
function hasStructuralKeywords(fragment: SchemaObject): boolean {
|
|
262
|
+
for (const key in fragment) {
|
|
263
|
+
if (structuralKeys.has(key as 'properties')) return true
|
|
264
|
+
}
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
|
|
257
268
|
export function flattenSchema(schema: SchemaObject | null): SchemaObject | null {
|
|
258
269
|
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null
|
|
259
|
-
if (schema.allOf.some((item) => isRef(item))) return schema
|
|
260
270
|
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
}
|
|
271
|
+
const allOfFragments = schema.allOf as SchemaObject[]
|
|
272
|
+
if (allOfFragments.some((item) => isRef(item))) return schema
|
|
273
|
+
if (allOfFragments.some(hasStructuralKeywords)) return schema
|
|
265
274
|
|
|
266
275
|
const merged: SchemaObject = { ...schema }
|
|
267
276
|
delete merged.allOf
|
|
268
277
|
|
|
269
|
-
for (const fragment of
|
|
278
|
+
for (const fragment of allOfFragments) {
|
|
270
279
|
for (const [key, value] of Object.entries(fragment)) {
|
|
271
280
|
if (merged[key as keyof typeof merged] === undefined) {
|
|
272
281
|
merged[key as keyof typeof merged] = value
|
|
@@ -311,10 +320,13 @@ function collectRefs(schema: unknown, refs = new Set<string>()): Set<string> {
|
|
|
311
320
|
}
|
|
312
321
|
|
|
313
322
|
if (schema && typeof schema === 'object') {
|
|
314
|
-
for (const
|
|
323
|
+
for (const key in schema) {
|
|
324
|
+
const value = (schema as Record<string, unknown>)[key]
|
|
315
325
|
if (key === '$ref' && typeof value === 'string') {
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
if (value.startsWith(SCHEMA_REF_PREFIX)) {
|
|
327
|
+
const name = value.slice(SCHEMA_REF_PREFIX.length)
|
|
328
|
+
if (name) refs.add(name)
|
|
329
|
+
}
|
|
318
330
|
} else {
|
|
319
331
|
collectRefs(value, refs)
|
|
320
332
|
}
|
|
@@ -424,11 +436,22 @@ export function getSchemas(document: Document, { contentType }: GetSchemasOption
|
|
|
424
436
|
|
|
425
437
|
const schemas: Record<string, SchemaObject> = {}
|
|
426
438
|
const nameMapping = new Map<string, string>()
|
|
427
|
-
const multipleSources = (items: SchemaWithMetadata[]) => new Set(items.map((i) => i.source)).size > 1
|
|
428
439
|
|
|
429
440
|
for (const [, items] of normalizedNames) {
|
|
441
|
+
const isSingle = items.length === 1
|
|
442
|
+
let hasMultipleSources = false
|
|
443
|
+
if (!isSingle) {
|
|
444
|
+
const firstSource = items[0]!.source
|
|
445
|
+
for (let i = 1; i < items.length; i++) {
|
|
446
|
+
if (items[i]!.source !== firstSource) {
|
|
447
|
+
hasMultipleSources = true
|
|
448
|
+
break
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
430
453
|
items.forEach((item, index) => {
|
|
431
|
-
const suffix =
|
|
454
|
+
const suffix = isSingle ? '' : hasMultipleSources ? getSemanticSuffix(item.source) : index === 0 ? '' : String(index + 1)
|
|
432
455
|
const uniqueName = item.originalName + suffix
|
|
433
456
|
schemas[uniqueName] = item.schema
|
|
434
457
|
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName)
|