@kubb/adapter-oas 5.0.0-alpha.2 → 5.0.0-alpha.20
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 +989 -1070
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +83 -128
- package/dist/index.js +988 -1069
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/adapter.ts +79 -63
- package/src/constants.ts +63 -63
- package/src/discriminator.ts +99 -0
- package/src/factory.ts +151 -0
- package/src/guards.ts +68 -0
- package/src/index.ts +1 -0
- package/src/parser.ts +352 -667
- package/src/refs.ts +57 -0
- package/src/resolvers.ts +490 -0
- package/src/types.ts +178 -56
- package/src/oas/Oas.ts +0 -616
- package/src/oas/resolveServerUrl.ts +0 -47
- package/src/oas/types.ts +0 -52
- package/src/oas/utils.ts +0 -402
package/src/oas/types.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// external packages
|
|
2
|
-
import type { Operation as OASOperation } from 'oas/operation'
|
|
3
|
-
import type {
|
|
4
|
-
DiscriminatorObject as OASDiscriminatorObject,
|
|
5
|
-
OASDocument,
|
|
6
|
-
HttpMethods as OASHttpMethods,
|
|
7
|
-
MediaTypeObject as OASMediaTypeObject,
|
|
8
|
-
ResponseObject as OASResponseObject,
|
|
9
|
-
SchemaObject as OASSchemaObject,
|
|
10
|
-
} from 'oas/types'
|
|
11
|
-
import type { OpenAPIV3 } from 'openapi-types'
|
|
12
|
-
|
|
13
|
-
export type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
|
|
14
|
-
|
|
15
|
-
export type contentType = 'application/json' | (string & {})
|
|
16
|
-
|
|
17
|
-
export type SchemaObject = OASSchemaObject & {
|
|
18
|
-
/**
|
|
19
|
-
* OAS 3.1 extension: allows marking a schema as nullable even when `type` does not include `'null'`.
|
|
20
|
-
*/
|
|
21
|
-
'x-nullable'?: boolean
|
|
22
|
-
/**
|
|
23
|
-
* OAS 3.1: constrains the schema to a single fixed value.
|
|
24
|
-
* Semantically equivalent to a one-item `enum`.
|
|
25
|
-
*/
|
|
26
|
-
const?: string | number | boolean | null
|
|
27
|
-
/**
|
|
28
|
-
* OAS 3.1: specifies the media type of the schema content.
|
|
29
|
-
* When set to `'application/octet-stream'` on a `string` schema, the schema is treated as binary (`blob`).
|
|
30
|
-
*/
|
|
31
|
-
contentMediaType?: string
|
|
32
|
-
$ref?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Re-exported from `constants.ts` for backwards compatibility.
|
|
37
|
-
*/
|
|
38
|
-
export { httpMethods as HttpMethods } from '../constants.ts'
|
|
39
|
-
|
|
40
|
-
export type HttpMethod = OASHttpMethods
|
|
41
|
-
|
|
42
|
-
export type Document = OASDocument
|
|
43
|
-
|
|
44
|
-
export type Operation = OASOperation
|
|
45
|
-
|
|
46
|
-
export type DiscriminatorObject = OASDiscriminatorObject
|
|
47
|
-
|
|
48
|
-
export type ReferenceObject = OpenAPIV3.ReferenceObject
|
|
49
|
-
|
|
50
|
-
export type ResponseObject = OASResponseObject
|
|
51
|
-
|
|
52
|
-
export type MediaTypeObject = OASMediaTypeObject
|
package/src/oas/utils.ts
DELETED
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import { pascalCase, URLPath } from '@internals/utils'
|
|
3
|
-
import type { Config } from '@kubb/core'
|
|
4
|
-
import { bundle, loadConfig } from '@redocly/openapi-core'
|
|
5
|
-
import yaml from '@stoplight/yaml'
|
|
6
|
-
import { isRef } from 'oas/types'
|
|
7
|
-
import OASNormalize from 'oas-normalize'
|
|
8
|
-
import type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
|
|
9
|
-
import { isPlainObject, mergeDeep } from 'remeda'
|
|
10
|
-
import swagger2openapi from 'swagger2openapi'
|
|
11
|
-
import { MERGE_DEFAULT_TITLE, MERGE_DEFAULT_VERSION, MERGE_OPENAPI_VERSION, structuralKeys } from '../constants.ts'
|
|
12
|
-
import { Oas } from './Oas.ts'
|
|
13
|
-
import type { contentType, Document, SchemaObject } from './types.ts'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Narrows `doc` to a Swagger 2.0 document.
|
|
17
|
-
* Swagger 2.0 documents do not have an `openapi` version key.
|
|
18
|
-
*/
|
|
19
|
-
export function isOpenApiV2Document(doc: unknown): doc is OpenAPIV2.Document {
|
|
20
|
-
return !!doc && isPlainObject(doc) && !('openapi' in doc)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Narrows `doc` to an OpenAPI 3.x document.
|
|
25
|
-
* Any document that has an `openapi` key is considered 3.x (including 3.1).
|
|
26
|
-
*/
|
|
27
|
-
export function isOpenApiV3Document(doc: unknown): doc is OpenAPIV3.Document {
|
|
28
|
-
return !!doc && isPlainObject(doc) && 'openapi' in doc
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Narrows `doc` to an OpenAPI 3.1 document by checking the version prefix.
|
|
33
|
-
*/
|
|
34
|
-
export function isOpenApiV3_1Document(doc: unknown): doc is OpenAPIV3_1.Document {
|
|
35
|
-
return !!doc && isPlainObject(doc) && 'openapi' in (doc as object) && (doc as { openapi: string }).openapi.startsWith('3.1')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Returns `true` when a schema should be treated as nullable.
|
|
40
|
-
*
|
|
41
|
-
* Covers three nullable signals across OAS versions:
|
|
42
|
-
* - OAS 3.0: `nullable: true` or the vendor extension `x-nullable: true`.
|
|
43
|
-
* - OAS 3.1 / JSON Schema: `type: 'null'` or `type: ['null', ...]` (multi-type array).
|
|
44
|
-
*/
|
|
45
|
-
export function isNullable(schema?: SchemaObject & { 'x-nullable'?: boolean }): boolean {
|
|
46
|
-
const explicitNullable = schema?.nullable ?? schema?.['x-nullable']
|
|
47
|
-
if (explicitNullable === true) return true
|
|
48
|
-
|
|
49
|
-
const schemaType = schema?.type
|
|
50
|
-
if (schemaType === 'null') return true
|
|
51
|
-
if (Array.isArray(schemaType)) return schemaType.includes('null')
|
|
52
|
-
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Narrows `obj` to an OpenAPI `$ref` pointer object.
|
|
58
|
-
* Delegates to the `oas` package's own `isRef` helper.
|
|
59
|
-
*/
|
|
60
|
-
export function isReference(obj?: unknown): obj is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {
|
|
61
|
-
return !!obj && isRef(obj as object)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Returns `true` when `obj` is a schema that carries a structured OAS 3.x `discriminator`
|
|
66
|
-
* object — as opposed to a plain-string discriminator found in some Swagger 2 specs.
|
|
67
|
-
*/
|
|
68
|
-
export function isDiscriminator(obj?: unknown): obj is SchemaObject & { discriminator: OpenAPIV3.DiscriminatorObject } {
|
|
69
|
-
const record = obj as Record<string, unknown>
|
|
70
|
-
return !!obj && !!record['discriminator'] && typeof record['discriminator'] !== 'string'
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Loads, dereferences, and wraps a raw OpenAPI document into an `Oas` instance.
|
|
75
|
-
*
|
|
76
|
-
* When given a file path string with `canBundle: true` (the default), Redocly's
|
|
77
|
-
* bundler resolves all external `$ref`s first. Swagger 2.0 documents are
|
|
78
|
-
* automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
|
|
79
|
-
*/
|
|
80
|
-
export async function parse(
|
|
81
|
-
pathOrApi: string | Document,
|
|
82
|
-
{ oasClass = Oas, canBundle = true, enablePaths = true }: { oasClass?: typeof Oas; canBundle?: boolean; enablePaths?: boolean } = {},
|
|
83
|
-
): Promise<Oas> {
|
|
84
|
-
if (typeof pathOrApi === 'string' && canBundle) {
|
|
85
|
-
const config = await loadConfig()
|
|
86
|
-
const bundleResults = await bundle({ ref: pathOrApi, config, base: pathOrApi })
|
|
87
|
-
|
|
88
|
-
return parse(bundleResults.bundle.parsed as string, { oasClass, canBundle, enablePaths })
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const oasNormalize = new OASNormalize(pathOrApi, {
|
|
92
|
-
enablePaths,
|
|
93
|
-
colorizeErrors: true,
|
|
94
|
-
})
|
|
95
|
-
const document = (await oasNormalize.load()) as Document
|
|
96
|
-
|
|
97
|
-
if (isOpenApiV2Document(document)) {
|
|
98
|
-
const { openapi } = await swagger2openapi.convertObj(document, {
|
|
99
|
-
anchors: true,
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
return new oasClass(openapi as Document)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return new oasClass(document)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Deep-merges multiple OpenAPI documents into a single `Oas` instance.
|
|
110
|
-
*
|
|
111
|
-
* Each document is parsed independently (without path dereferencing) and then
|
|
112
|
-
* recursively merged using `remeda`'s `mergeDeep`. The result is re-parsed so
|
|
113
|
-
* the returned `Oas` instance is fully initialized.
|
|
114
|
-
*
|
|
115
|
-
* Throws when the input array is empty — at least one document is required.
|
|
116
|
-
*/
|
|
117
|
-
export async function merge(pathOrApi: Array<string | Document>, { oasClass = Oas }: { oasClass?: typeof Oas } = {}): Promise<Oas> {
|
|
118
|
-
const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false, canBundle: false })))
|
|
119
|
-
|
|
120
|
-
if (instances.length === 0) {
|
|
121
|
-
throw new Error('No OAS instances provided for merging.')
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const seed: Document = {
|
|
125
|
-
openapi: MERGE_OPENAPI_VERSION,
|
|
126
|
-
info: { title: MERGE_DEFAULT_TITLE, version: MERGE_DEFAULT_VERSION },
|
|
127
|
-
paths: {},
|
|
128
|
-
components: { schemas: {} },
|
|
129
|
-
} as Document
|
|
130
|
-
|
|
131
|
-
const merged = instances.reduce((acc, current) => mergeDeep(acc, current.document as Document), seed)
|
|
132
|
-
|
|
133
|
-
return parse(merged, { oasClass })
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Constructs an `Oas` instance from a Kubb `Config` object.
|
|
138
|
-
*
|
|
139
|
-
* Handles all three input forms supported by `Config`:
|
|
140
|
-
* - `data` — an inline YAML string, JSON string, or pre-parsed object.
|
|
141
|
-
* - Array — multiple file paths that are deep-merged via {@link merge}.
|
|
142
|
-
* - `path` — a local file path or a remote URL.
|
|
143
|
-
*/
|
|
144
|
-
export function parseFromConfig(config: Config, oasClass: typeof Oas = Oas): Promise<Oas> {
|
|
145
|
-
if ('data' in config.input) {
|
|
146
|
-
if (typeof config.input.data === 'object') {
|
|
147
|
-
return parse(structuredClone(config.input.data) as Document, { oasClass })
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const api: string = yaml.parse(config.input.data as string)
|
|
152
|
-
return parse(api, { oasClass })
|
|
153
|
-
} catch {
|
|
154
|
-
return parse(config.input.data as string, { oasClass })
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (Array.isArray(config.input)) {
|
|
159
|
-
return merge(
|
|
160
|
-
config.input.map((input) => path.resolve(config.root, input.path)),
|
|
161
|
-
{ oasClass },
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (new URLPath(config.input.path).isURL) {
|
|
166
|
-
return parse(config.input.path, { oasClass })
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return parse(path.resolve(config.root, config.input.path), { oasClass })
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Flattens a single-member or keyword-only `allOf` into its parent schema.
|
|
174
|
-
*
|
|
175
|
-
* Flattening is only performed when every `allOf` member is a "plain fragment" —
|
|
176
|
-
* i.e. contains no structural composition keywords (see `structuralKeys`) and no
|
|
177
|
-
* `$ref`. When any member carries structural meaning, the schema is returned unchanged.
|
|
178
|
-
*
|
|
179
|
-
* Keyword values from allOf members are merged into the parent on a first-write basis:
|
|
180
|
-
* outer schema values always win over fragment values.
|
|
181
|
-
*/
|
|
182
|
-
export function flattenSchema(schema: SchemaObject | null): SchemaObject | null {
|
|
183
|
-
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null
|
|
184
|
-
if (schema.allOf.some((item) => isRef(item))) return schema
|
|
185
|
-
|
|
186
|
-
const isPlainFragment = (item: SchemaObject) => !Object.keys(item).some((key) => structuralKeys.has(key))
|
|
187
|
-
if (!schema.allOf.every((item) => isPlainFragment(item as SchemaObject))) return schema
|
|
188
|
-
|
|
189
|
-
const merged: SchemaObject = { ...schema }
|
|
190
|
-
delete merged.allOf
|
|
191
|
-
|
|
192
|
-
for (const fragment of schema.allOf as SchemaObject[]) {
|
|
193
|
-
for (const [key, value] of Object.entries(fragment)) {
|
|
194
|
-
if (merged[key as keyof typeof merged] === undefined) {
|
|
195
|
-
merged[key as keyof typeof merged] = value
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return merged
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Validates an OpenAPI document using `oas-normalize`.
|
|
205
|
-
* Enables path validation and colorized error output.
|
|
206
|
-
*/
|
|
207
|
-
export async function validate(document: Document) {
|
|
208
|
-
const oasNormalize = new OASNormalize(document, {
|
|
209
|
-
enablePaths: true,
|
|
210
|
-
colorizeErrors: true,
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
return oasNormalize.validate({
|
|
214
|
-
parser: {
|
|
215
|
-
validate: {
|
|
216
|
-
errors: { colorize: true },
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Discriminates the three component sources Kubb reads schemas from.
|
|
224
|
-
*/
|
|
225
|
-
type SchemaSourceMode = 'schemas' | 'responses' | 'requestBodies'
|
|
226
|
-
|
|
227
|
-
export type SchemaWithMetadata = {
|
|
228
|
-
schema: SchemaObject
|
|
229
|
-
source: SchemaSourceMode
|
|
230
|
-
originalName: string
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
type GetSchemasResult = {
|
|
234
|
-
schemas: Record<string, SchemaObject>
|
|
235
|
-
nameMapping: Map<string, string>
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` refs.
|
|
240
|
-
* Used by `sortSchemas` to build the dependency graph.
|
|
241
|
-
*/
|
|
242
|
-
function collectRefs(schema: unknown, refs = new Set<string>()): Set<string> {
|
|
243
|
-
if (Array.isArray(schema)) {
|
|
244
|
-
for (const item of schema) collectRefs(item, refs)
|
|
245
|
-
return refs
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (schema && typeof schema === 'object') {
|
|
249
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
250
|
-
if (key === '$ref' && typeof value === 'string') {
|
|
251
|
-
const match = value.match(/^#\/components\/schemas\/(.+)$/)
|
|
252
|
-
if (match) refs.add(match[1]!)
|
|
253
|
-
} else {
|
|
254
|
-
collectRefs(value, refs)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return refs
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
264
|
-
*
|
|
265
|
-
* Schemas with no references come first; schemas that are referenced by others
|
|
266
|
-
* come before those that reference them. This ensures code generators emit
|
|
267
|
-
* referenced types before the types that depend on them.
|
|
268
|
-
*
|
|
269
|
-
* Cycles are silently skipped via the `stack` guard inside `visit`.
|
|
270
|
-
*/
|
|
271
|
-
export function sortSchemas(schemas: Record<string, SchemaObject>): Record<string, SchemaObject> {
|
|
272
|
-
const deps = new Map<string, string[]>()
|
|
273
|
-
|
|
274
|
-
for (const [name, schema] of Object.entries(schemas)) {
|
|
275
|
-
deps.set(name, Array.from(collectRefs(schema)))
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const sorted: string[] = []
|
|
279
|
-
const visited = new Set<string>()
|
|
280
|
-
|
|
281
|
-
function visit(name: string, stack: Set<string>) {
|
|
282
|
-
if (visited.has(name) || stack.has(name)) return
|
|
283
|
-
stack.add(name)
|
|
284
|
-
for (const child of deps.get(name) ?? []) {
|
|
285
|
-
if (deps.has(child)) visit(child, stack)
|
|
286
|
-
}
|
|
287
|
-
stack.delete(name)
|
|
288
|
-
visited.add(name)
|
|
289
|
-
sorted.push(name)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
for (const name of Object.keys(schemas)) {
|
|
293
|
-
visit(name, new Set())
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const result: Record<string, SchemaObject> = {}
|
|
297
|
-
for (const name of sorted) result[name] = schemas[name]!
|
|
298
|
-
return result
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Extracts the inline schema from a media-type `content` map.
|
|
303
|
-
*
|
|
304
|
-
* Prefers `preferredContentType` when provided; otherwise falls back to the
|
|
305
|
-
* first key in the map. Returns `null` when:
|
|
306
|
-
* - `content` is absent.
|
|
307
|
-
* - The target content-type has no `schema` entry.
|
|
308
|
-
* - The schema is a `$ref` (callers resolve refs separately).
|
|
309
|
-
*/
|
|
310
|
-
export function extractSchemaFromContent(content: Record<string, unknown> | undefined, preferredContentType?: contentType): SchemaObject | null {
|
|
311
|
-
if (!content) return null
|
|
312
|
-
|
|
313
|
-
const firstContentType = Object.keys(content)[0] ?? 'application/json'
|
|
314
|
-
const targetContentType = preferredContentType ?? firstContentType
|
|
315
|
-
const contentSchema = content[targetContentType] as { schema?: SchemaObject } | undefined
|
|
316
|
-
const schema = contentSchema?.schema
|
|
317
|
-
|
|
318
|
-
if (schema && '$ref' in schema) return null
|
|
319
|
-
return schema ?? null
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Returns the PascalCase suffix appended to a component name when resolving
|
|
324
|
-
* cross-source name collisions (schemas vs. responses vs. requestBodies).
|
|
325
|
-
*/
|
|
326
|
-
function getSemanticSuffix(source: SchemaSourceMode): string {
|
|
327
|
-
switch (source) {
|
|
328
|
-
case 'schemas':
|
|
329
|
-
return 'Schema'
|
|
330
|
-
case 'responses':
|
|
331
|
-
return 'Response'
|
|
332
|
-
case 'requestBodies':
|
|
333
|
-
return 'Request'
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Builds `GetSchemasResult` without any collision detection.
|
|
339
|
-
* Each schema is registered under its original component name and its full
|
|
340
|
-
* `#/components/<source>/<name>` ref path.
|
|
341
|
-
*/
|
|
342
|
-
export function legacyResolve(schemasWithMeta: SchemaWithMetadata[]): GetSchemasResult {
|
|
343
|
-
const schemas: Record<string, SchemaObject> = {}
|
|
344
|
-
const nameMapping = new Map<string, string>()
|
|
345
|
-
|
|
346
|
-
for (const item of schemasWithMeta) {
|
|
347
|
-
schemas[item.originalName] = item.schema
|
|
348
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return { schemas, nameMapping }
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Builds `GetSchemasResult` with automatic name-collision resolution.
|
|
356
|
-
*
|
|
357
|
-
* When two or more schemas normalize to the same PascalCase name:
|
|
358
|
-
* - If they share the same source, a numeric suffix (`2`, `3`, …) is appended.
|
|
359
|
-
* - If they come from different sources (schemas / responses / requestBodies),
|
|
360
|
-
* a semantic suffix (`Schema`, `Response`, `Request`) is appended.
|
|
361
|
-
*
|
|
362
|
-
* Non-colliding schemas are left unchanged.
|
|
363
|
-
*/
|
|
364
|
-
export function resolveCollisions(schemasWithMeta: SchemaWithMetadata[]): GetSchemasResult {
|
|
365
|
-
const schemas: Record<string, SchemaObject> = {}
|
|
366
|
-
const nameMapping = new Map<string, string>()
|
|
367
|
-
const normalizedNames = new Map<string, SchemaWithMetadata[]>()
|
|
368
|
-
|
|
369
|
-
for (const item of schemasWithMeta) {
|
|
370
|
-
const normalized = pascalCase(item.originalName)
|
|
371
|
-
const bucket = normalizedNames.get(normalized) ?? []
|
|
372
|
-
bucket.push(item)
|
|
373
|
-
normalizedNames.set(normalized, bucket)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
for (const [, items] of normalizedNames) {
|
|
377
|
-
if (items.length === 1) {
|
|
378
|
-
const item = items[0]!
|
|
379
|
-
schemas[item.originalName] = item.schema
|
|
380
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName)
|
|
381
|
-
continue
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const sources = new Set(items.map((item) => item.source))
|
|
385
|
-
|
|
386
|
-
if (sources.size === 1) {
|
|
387
|
-
items.forEach((item, index) => {
|
|
388
|
-
const uniqueName = item.originalName + (index === 0 ? '' : (index + 1).toString())
|
|
389
|
-
schemas[uniqueName] = item.schema
|
|
390
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName)
|
|
391
|
-
})
|
|
392
|
-
} else {
|
|
393
|
-
items.forEach((item) => {
|
|
394
|
-
const uniqueName = item.originalName + getSemanticSuffix(item.source)
|
|
395
|
-
schemas[uniqueName] = item.schema
|
|
396
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName)
|
|
397
|
-
})
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return { schemas, nameMapping }
|
|
402
|
-
}
|