@kubb/adapter-oas 5.0.0-beta.8 → 5.0.0-beta.80
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/LICENSE +17 -10
- package/README.md +26 -24
- package/dist/index.cjs +1416 -871
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +844 -78
- package/dist/index.js +1419 -869
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
- package/extension.yaml +0 -344
- package/src/adapter.ts +0 -130
- package/src/constants.ts +0 -122
- package/src/discriminator.ts +0 -108
- package/src/factory.ts +0 -162
- package/src/guards.ts +0 -68
- package/src/index.ts +0 -18
- package/src/parser.ts +0 -1002
- package/src/refs.ts +0 -74
- package/src/resolvers.ts +0 -544
- package/src/types.ts +0 -206
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/src/parser.ts
DELETED
|
@@ -1,1002 +0,0 @@
|
|
|
1
|
-
import { URLPath } from '@internals/utils'
|
|
2
|
-
import { ast } from '@kubb/core'
|
|
3
|
-
import BaseOas from 'oas'
|
|
4
|
-
import { DEFAULT_PARSER_OPTIONS, enumExtensionKeys, SCHEMA_REF_PREFIX, typeOptionMap } from './constants.ts'
|
|
5
|
-
import { isDiscriminator, isNullable, isReference } from './guards.ts'
|
|
6
|
-
import { resolveRef } from './refs.ts'
|
|
7
|
-
import {
|
|
8
|
-
buildSchemaNode,
|
|
9
|
-
flattenSchema,
|
|
10
|
-
getDateType,
|
|
11
|
-
getMediaType,
|
|
12
|
-
getParameters,
|
|
13
|
-
getPrimitiveType,
|
|
14
|
-
getRequestBodyContentTypes,
|
|
15
|
-
getRequestSchema,
|
|
16
|
-
getResponseSchema,
|
|
17
|
-
getSchemas,
|
|
18
|
-
getSchemaType,
|
|
19
|
-
} from './resolvers.ts'
|
|
20
|
-
import type { ContentType, Document, Operation, ReferenceObject, SchemaObject } from './types.ts'
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Parser context holding the raw OpenAPI document and optional content-type override.
|
|
24
|
-
*
|
|
25
|
-
* Passed to schema and operation converters to access the full specification
|
|
26
|
-
* and handle content negotiation when multiple media types are available.
|
|
27
|
-
*/
|
|
28
|
-
export type OasParserContext = {
|
|
29
|
-
document: Document
|
|
30
|
-
contentType?: ContentType
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Pre-computed per-schema context passed to every schema converter.
|
|
35
|
-
*
|
|
36
|
-
* Centralizes schema derivations (type resolution, defaults, options) to avoid repeated
|
|
37
|
-
* computation across all conversion branches. The `type` field is normalized from OAS 3.1
|
|
38
|
-
* multi-type arrays to a single string.
|
|
39
|
-
*/
|
|
40
|
-
type SchemaContext = {
|
|
41
|
-
schema: SchemaObject
|
|
42
|
-
name: string | null | undefined
|
|
43
|
-
nullable: true | undefined
|
|
44
|
-
defaultValue: unknown
|
|
45
|
-
/**
|
|
46
|
-
* Normalized single type string (first element when OAS 3.1 multi-type array).
|
|
47
|
-
*/
|
|
48
|
-
type: string | undefined
|
|
49
|
-
rawOptions: Partial<ast.ParserOptions> | undefined
|
|
50
|
-
options: ast.ParserOptions
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
|
|
55
|
-
*
|
|
56
|
-
* This pattern violates the OpenAPI spec but appears in real specs. The fix moves enum values
|
|
57
|
-
* from the array to its items sub-schema, making them valid for downstream processing.
|
|
58
|
-
*
|
|
59
|
-
* @note This is a defensive measure for robustness with non-compliant specs.
|
|
60
|
-
*/
|
|
61
|
-
function normalizeArrayEnum(schema: SchemaObject): SchemaObject {
|
|
62
|
-
const isItemsObject = typeof schema.items === 'object' && !Array.isArray(schema.items)
|
|
63
|
-
const normalizedItems: SchemaObject = {
|
|
64
|
-
...(isItemsObject ? (schema.items as SchemaObject) : {}),
|
|
65
|
-
enum: schema.enum,
|
|
66
|
-
}
|
|
67
|
-
const { enum: _enum, ...schemaWithoutEnum } = schema
|
|
68
|
-
|
|
69
|
-
return { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Factory function that creates schema and operation converters for a given OpenAPI context.
|
|
74
|
-
*
|
|
75
|
-
* Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
|
|
76
|
-
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
77
|
-
* made possible by hoisting of function declarations.
|
|
78
|
-
*
|
|
79
|
-
* @note Not exported; called internally by `parseOas()` and `parseSchema()`.
|
|
80
|
-
*/
|
|
81
|
-
function createSchemaParser(ctx: OasParserContext) {
|
|
82
|
-
const document = ctx.document
|
|
83
|
-
|
|
84
|
-
// Branch handlers — each converts one OAS schema pattern to a SchemaNode.
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
88
|
-
* recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
|
|
89
|
-
*/
|
|
90
|
-
const resolvingRefs = new Set<string>()
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
94
|
-
*
|
|
95
|
-
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
96
|
-
* (description, readOnly, nullable, etc.) are stored directly on the ref node.
|
|
97
|
-
* Use `syncSchemaRef(node)` in printers to get a merged view of both.
|
|
98
|
-
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
99
|
-
*/
|
|
100
|
-
function convertRef({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): ast.SchemaNode {
|
|
101
|
-
let resolvedSchema: ast.SchemaNode | undefined
|
|
102
|
-
const refPath = schema.$ref
|
|
103
|
-
if (refPath && !resolvingRefs.has(refPath)) {
|
|
104
|
-
try {
|
|
105
|
-
const referenced = resolveRef<SchemaObject>(document, refPath)
|
|
106
|
-
if (referenced) {
|
|
107
|
-
resolvingRefs.add(refPath)
|
|
108
|
-
resolvedSchema = parseSchema({ schema: referenced }, rawOptions)
|
|
109
|
-
resolvingRefs.delete(refPath)
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
// Ref cannot be resolved in this document (e.g. unit tests with minimal documents).
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return ast.createSchema({
|
|
117
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
118
|
-
type: 'ref',
|
|
119
|
-
name: ast.extractRefName(schema.$ref!),
|
|
120
|
-
ref: schema.$ref,
|
|
121
|
-
schema: resolvedSchema,
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
|
|
127
|
-
*/
|
|
128
|
-
function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): ast.SchemaNode {
|
|
129
|
-
if (
|
|
130
|
-
schema.allOf!.length === 1 &&
|
|
131
|
-
!schema.properties &&
|
|
132
|
-
!(Array.isArray(schema.required) && schema.required.length) &&
|
|
133
|
-
schema.additionalProperties === undefined
|
|
134
|
-
) {
|
|
135
|
-
const [memberSchema] = schema.allOf as Array<SchemaObject | ReferenceObject>
|
|
136
|
-
const memberNode = parseSchema({ schema: memberSchema! as SchemaObject, name: null }, rawOptions)
|
|
137
|
-
const { kind: _kind, ...memberNodeProps } = memberNode
|
|
138
|
-
const mergedNullable = nullable || memberNode.nullable || undefined
|
|
139
|
-
const mergedDefault = schema.default === null && mergedNullable ? undefined : (schema.default ?? memberNode.default)
|
|
140
|
-
|
|
141
|
-
return ast.createSchema({
|
|
142
|
-
...memberNodeProps,
|
|
143
|
-
name,
|
|
144
|
-
title: schema.title ?? memberNode.title,
|
|
145
|
-
description: schema.description ?? memberNode.description,
|
|
146
|
-
deprecated: schema.deprecated ?? memberNode.deprecated,
|
|
147
|
-
nullable: mergedNullable,
|
|
148
|
-
readOnly: schema.readOnly ?? memberNode.readOnly,
|
|
149
|
-
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
150
|
-
default: mergedDefault,
|
|
151
|
-
example: schema.example ?? memberNode.example,
|
|
152
|
-
pattern: schema.pattern ?? ('pattern' in memberNode ? memberNode.pattern : undefined),
|
|
153
|
-
} as ast.DistributiveOmit<ast.SchemaNode, 'kind'>)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const filteredDiscriminantValues: Array<{
|
|
157
|
-
propertyName: string
|
|
158
|
-
value: string
|
|
159
|
-
}> = []
|
|
160
|
-
const allOfMembers: Array<ast.SchemaNode> = (schema.allOf as Array<SchemaObject | ReferenceObject>)
|
|
161
|
-
.filter((item) => {
|
|
162
|
-
if (!isReference(item) || !name) return true
|
|
163
|
-
const deref = resolveRef<SchemaObject>(document, item.$ref)
|
|
164
|
-
if (!deref || !isDiscriminator(deref)) return true
|
|
165
|
-
const parentUnion = deref.oneOf ?? deref.anyOf
|
|
166
|
-
if (!parentUnion) return true
|
|
167
|
-
const childRef = `${SCHEMA_REF_PREFIX}${name}`
|
|
168
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef)
|
|
169
|
-
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef)
|
|
170
|
-
if (inOneOf || inMapping) {
|
|
171
|
-
const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef)
|
|
172
|
-
if (discriminatorValue) {
|
|
173
|
-
filteredDiscriminantValues.push({
|
|
174
|
-
propertyName: deref.discriminator.propertyName,
|
|
175
|
-
value: discriminatorValue,
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
return false
|
|
179
|
-
}
|
|
180
|
-
return true
|
|
181
|
-
})
|
|
182
|
-
.map((s) => parseSchema({ schema: s as SchemaObject }, rawOptions))
|
|
183
|
-
|
|
184
|
-
const syntheticStart = allOfMembers.length
|
|
185
|
-
|
|
186
|
-
if (Array.isArray(schema.required) && schema.required.length) {
|
|
187
|
-
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : new Set<string>()
|
|
188
|
-
const missingRequired = schema.required.filter((key) => !outerKeys.has(key))
|
|
189
|
-
|
|
190
|
-
if (missingRequired.length) {
|
|
191
|
-
const resolvedMembers = (schema.allOf as Array<SchemaObject | ReferenceObject>).flatMap((item) => {
|
|
192
|
-
if (!isReference(item)) return [item as SchemaObject]
|
|
193
|
-
const deref = resolveRef<SchemaObject>(document, item.$ref)
|
|
194
|
-
return deref && !isReference(deref) ? [deref] : []
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
for (const key of missingRequired) {
|
|
198
|
-
for (const resolved of resolvedMembers) {
|
|
199
|
-
if (resolved.properties?.[key]) {
|
|
200
|
-
allOfMembers.push(
|
|
201
|
-
parseSchema(
|
|
202
|
-
{
|
|
203
|
-
schema: {
|
|
204
|
-
properties: { [key]: resolved.properties[key] },
|
|
205
|
-
required: [key],
|
|
206
|
-
} as SchemaObject,
|
|
207
|
-
},
|
|
208
|
-
rawOptions,
|
|
209
|
-
),
|
|
210
|
-
)
|
|
211
|
-
break
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (schema.properties) {
|
|
219
|
-
const { allOf: _allOf, ...schemaWithoutAllOf } = schema
|
|
220
|
-
allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions))
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
for (const { propertyName, value } of filteredDiscriminantValues) {
|
|
224
|
-
allOfMembers.push(ast.createDiscriminantNode({ propertyName, value }))
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return ast.createSchema({
|
|
228
|
-
type: 'intersection',
|
|
229
|
-
members: [...ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
|
|
230
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
|
|
236
|
-
*/
|
|
237
|
-
function convertUnion({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): ast.SchemaNode {
|
|
238
|
-
function pickDiscriminatorPropertyNode(node: ast.SchemaNode, propertyName: string): ast.SchemaNode | null {
|
|
239
|
-
const objectNode = ast.narrowSchema(node, 'object')
|
|
240
|
-
const discriminatorProperty = objectNode?.properties?.find((property) => property.name === propertyName)
|
|
241
|
-
|
|
242
|
-
if (!discriminatorProperty) {
|
|
243
|
-
return null
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return ast.createSchema({
|
|
247
|
-
type: 'object',
|
|
248
|
-
primitive: 'object',
|
|
249
|
-
properties: [discriminatorProperty],
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const unionMembers = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])]
|
|
254
|
-
const strategy: 'one' | 'any' = schema.oneOf ? 'one' : 'any'
|
|
255
|
-
const unionBase = {
|
|
256
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
257
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
258
|
-
strategy,
|
|
259
|
-
}
|
|
260
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : undefined
|
|
261
|
-
const sharedPropertiesNode = schema.properties
|
|
262
|
-
? (() => {
|
|
263
|
-
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema
|
|
264
|
-
const memberBaseSchema: SchemaObject = discriminator
|
|
265
|
-
? (Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== 'discriminator')) as SchemaObject)
|
|
266
|
-
: schemaWithoutUnion
|
|
267
|
-
return parseSchema({ schema: memberBaseSchema, name }, rawOptions)
|
|
268
|
-
})()
|
|
269
|
-
: undefined
|
|
270
|
-
|
|
271
|
-
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
272
|
-
const members = unionMembers.map((s) => {
|
|
273
|
-
const ref = isReference(s) ? s.$ref : undefined
|
|
274
|
-
const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref)
|
|
275
|
-
const memberNode = parseSchema({ schema: s as SchemaObject }, rawOptions)
|
|
276
|
-
|
|
277
|
-
if (!discriminatorValue || !discriminator) {
|
|
278
|
-
return memberNode
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const narrowedDiscriminatorNode = sharedPropertiesNode
|
|
282
|
-
? pickDiscriminatorPropertyNode(
|
|
283
|
-
ast.setDiscriminatorEnum({
|
|
284
|
-
node: sharedPropertiesNode,
|
|
285
|
-
propertyName: discriminator.propertyName,
|
|
286
|
-
values: [discriminatorValue],
|
|
287
|
-
}),
|
|
288
|
-
discriminator.propertyName,
|
|
289
|
-
)
|
|
290
|
-
: undefined
|
|
291
|
-
|
|
292
|
-
return ast.createSchema({
|
|
293
|
-
type: 'intersection',
|
|
294
|
-
members: [
|
|
295
|
-
memberNode,
|
|
296
|
-
narrowedDiscriminatorNode ??
|
|
297
|
-
ast.createDiscriminantNode({
|
|
298
|
-
propertyName: discriminator.propertyName,
|
|
299
|
-
value: discriminatorValue,
|
|
300
|
-
}),
|
|
301
|
-
],
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
const unionNode = ast.createSchema({
|
|
306
|
-
type: 'union',
|
|
307
|
-
...unionBase,
|
|
308
|
-
members,
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
if (!sharedPropertiesNode) {
|
|
312
|
-
return unionNode
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return ast.createSchema({
|
|
316
|
-
type: 'intersection',
|
|
317
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
318
|
-
members: [unionNode, sharedPropertiesNode],
|
|
319
|
-
})
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return ast.createSchema({
|
|
323
|
-
type: 'union',
|
|
324
|
-
...unionBase,
|
|
325
|
-
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s as SchemaObject }, rawOptions))),
|
|
326
|
-
})
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
|
|
331
|
-
*/
|
|
332
|
-
function convertConst({ schema, name, nullable, defaultValue }: SchemaContext): ast.SchemaNode {
|
|
333
|
-
const constValue = schema.const
|
|
334
|
-
|
|
335
|
-
if (constValue === null) {
|
|
336
|
-
return ast.createSchema({
|
|
337
|
-
type: 'null',
|
|
338
|
-
primitive: 'null',
|
|
339
|
-
name,
|
|
340
|
-
title: schema.title,
|
|
341
|
-
description: schema.description,
|
|
342
|
-
deprecated: schema.deprecated,
|
|
343
|
-
})
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const constPrimitive = getPrimitiveType(typeof constValue === 'number' ? 'number' : typeof constValue === 'boolean' ? 'boolean' : 'string')
|
|
347
|
-
return ast.createSchema({
|
|
348
|
-
type: 'enum',
|
|
349
|
-
primitive: constPrimitive,
|
|
350
|
-
enumValues: [constValue as string | number | boolean],
|
|
351
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
352
|
-
})
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Converts a format-annotated schema into a special-type `SchemaNode`.
|
|
357
|
-
* Returns `null` when the format should fall through to string handling (`dateType: false`).
|
|
358
|
-
*/
|
|
359
|
-
function convertFormat({ schema, name, nullable, defaultValue, options }: SchemaContext): ast.SchemaNode | null {
|
|
360
|
-
const base = buildSchemaNode(schema, name, nullable, defaultValue)
|
|
361
|
-
|
|
362
|
-
if (schema.format === 'int64') {
|
|
363
|
-
return ast.createSchema({
|
|
364
|
-
type: options.integerType === 'bigint' ? 'bigint' : 'integer',
|
|
365
|
-
primitive: 'integer',
|
|
366
|
-
...base,
|
|
367
|
-
min: schema.minimum,
|
|
368
|
-
max: schema.maximum,
|
|
369
|
-
exclusiveMinimum: typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined,
|
|
370
|
-
exclusiveMaximum: typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined,
|
|
371
|
-
})
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (schema.format === 'date-time' || schema.format === 'date' || schema.format === 'time') {
|
|
375
|
-
const dateType = getDateType(options, schema.format)
|
|
376
|
-
if (!dateType) return null
|
|
377
|
-
|
|
378
|
-
if (dateType.type === 'datetime') {
|
|
379
|
-
return ast.createSchema({
|
|
380
|
-
...base,
|
|
381
|
-
primitive: 'string' as const,
|
|
382
|
-
type: 'datetime',
|
|
383
|
-
offset: dateType.offset,
|
|
384
|
-
local: dateType.local,
|
|
385
|
-
})
|
|
386
|
-
}
|
|
387
|
-
return ast.createSchema({
|
|
388
|
-
...base,
|
|
389
|
-
primitive: 'string' as const,
|
|
390
|
-
type: dateType.type,
|
|
391
|
-
representation: dateType.representation,
|
|
392
|
-
})
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const specialType = getSchemaType(schema.format!)
|
|
396
|
-
if (!specialType) return null
|
|
397
|
-
|
|
398
|
-
const specialPrimitive: ast.PrimitiveSchemaType = specialType === 'number' || specialType === 'integer' || specialType === 'bigint' ? specialType : 'string'
|
|
399
|
-
|
|
400
|
-
if (specialType === 'number' || specialType === 'integer' || specialType === 'bigint') {
|
|
401
|
-
return ast.createSchema({
|
|
402
|
-
...base,
|
|
403
|
-
primitive: specialPrimitive,
|
|
404
|
-
type: specialType,
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
if (specialType === 'url') {
|
|
408
|
-
return ast.createSchema({
|
|
409
|
-
...base,
|
|
410
|
-
primitive: 'string' as const,
|
|
411
|
-
type: 'url',
|
|
412
|
-
min: schema.minLength,
|
|
413
|
-
max: schema.maxLength,
|
|
414
|
-
})
|
|
415
|
-
}
|
|
416
|
-
if (specialType === 'ipv4') {
|
|
417
|
-
return ast.createSchema({
|
|
418
|
-
...base,
|
|
419
|
-
primitive: 'string' as const,
|
|
420
|
-
type: 'ipv4',
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
if (specialType === 'ipv6') {
|
|
424
|
-
return ast.createSchema({
|
|
425
|
-
...base,
|
|
426
|
-
primitive: 'string' as const,
|
|
427
|
-
type: 'ipv6',
|
|
428
|
-
})
|
|
429
|
-
}
|
|
430
|
-
if (specialType === 'uuid' || specialType === 'email') {
|
|
431
|
-
return ast.createSchema({
|
|
432
|
-
...base,
|
|
433
|
-
primitive: 'string' as const,
|
|
434
|
-
type: specialType,
|
|
435
|
-
min: schema.minLength,
|
|
436
|
-
max: schema.maxLength,
|
|
437
|
-
})
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return ast.createSchema({
|
|
441
|
-
...base,
|
|
442
|
-
primitive: specialPrimitive,
|
|
443
|
-
type: specialType as ast.ScalarSchemaType,
|
|
444
|
-
})
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Converts an `enum` schema into an `EnumSchemaNode`.
|
|
449
|
-
*/
|
|
450
|
-
function convertEnum({ schema, name, nullable, type, rawOptions }: SchemaContext): ast.SchemaNode {
|
|
451
|
-
if (type === 'array') {
|
|
452
|
-
return parseSchema({ schema: normalizeArrayEnum(schema), name }, rawOptions)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const nullInEnum = schema.enum!.includes(null)
|
|
456
|
-
const filteredValues = (nullInEnum ? schema.enum!.filter((v) => v !== null) : schema.enum!) as Array<string | number | boolean>
|
|
457
|
-
const enumNullable = nullable || nullInEnum || undefined
|
|
458
|
-
const enumDefault = schema.default === null && enumNullable ? undefined : schema.default
|
|
459
|
-
const enumPrimitive = getPrimitiveType(type)
|
|
460
|
-
|
|
461
|
-
const enumBase = {
|
|
462
|
-
type: 'enum' as const,
|
|
463
|
-
primitive: enumPrimitive,
|
|
464
|
-
name,
|
|
465
|
-
title: schema.title,
|
|
466
|
-
description: schema.description,
|
|
467
|
-
deprecated: schema.deprecated,
|
|
468
|
-
nullable: enumNullable,
|
|
469
|
-
readOnly: schema.readOnly,
|
|
470
|
-
writeOnly: schema.writeOnly,
|
|
471
|
-
default: enumDefault,
|
|
472
|
-
example: schema.example,
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const extensionKey = enumExtensionKeys.find((key) => key in schema)
|
|
476
|
-
if (extensionKey || enumPrimitive === 'number' || enumPrimitive === 'integer' || enumPrimitive === 'boolean') {
|
|
477
|
-
const enumPrimitiveType = (enumPrimitive === 'number' || enumPrimitive === 'integer' ? 'number' : enumPrimitive === 'boolean' ? 'boolean' : 'string') as
|
|
478
|
-
| 'number'
|
|
479
|
-
| 'boolean'
|
|
480
|
-
| 'string'
|
|
481
|
-
const rawEnumNames = extensionKey ? ((schema as Record<string, unknown>)[extensionKey] as Array<string | number>) : undefined
|
|
482
|
-
const uniqueValues = [...new Set(filteredValues)]
|
|
483
|
-
const seenNames = new Set<string>()
|
|
484
|
-
|
|
485
|
-
return ast.createSchema({
|
|
486
|
-
...enumBase,
|
|
487
|
-
primitive: enumPrimitiveType,
|
|
488
|
-
namedEnumValues: uniqueValues
|
|
489
|
-
.map((value, index) => ({
|
|
490
|
-
name: String(rawEnumNames?.[index] ?? value),
|
|
491
|
-
value,
|
|
492
|
-
primitive: enumPrimitiveType,
|
|
493
|
-
}))
|
|
494
|
-
.filter((entry) => {
|
|
495
|
-
if (seenNames.has(entry.name)) return false
|
|
496
|
-
seenNames.add(entry.name)
|
|
497
|
-
return true
|
|
498
|
-
}),
|
|
499
|
-
})
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return ast.createSchema({
|
|
503
|
-
...enumBase,
|
|
504
|
-
enumValues: [...new Set(filteredValues)],
|
|
505
|
-
})
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* Converts an object-like schema into an `ObjectSchemaNode`.
|
|
510
|
-
*/
|
|
511
|
-
function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }: SchemaContext): ast.SchemaNode {
|
|
512
|
-
const properties: Array<ast.PropertyNode> = schema.properties
|
|
513
|
-
? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
514
|
-
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required
|
|
515
|
-
const resolvedPropSchema = propSchema as SchemaObject
|
|
516
|
-
const propNullable = isNullable(resolvedPropSchema)
|
|
517
|
-
|
|
518
|
-
const resolvedChildName = ast.childName(name, propName)
|
|
519
|
-
const propNode = parseSchema({ schema: resolvedPropSchema, name: resolvedChildName }, rawOptions)
|
|
520
|
-
let schemaNode = ast.setEnumName(propNode, name, propName, options.enumSuffix)
|
|
521
|
-
|
|
522
|
-
const tupleNode = ast.narrowSchema(schemaNode, 'tuple')
|
|
523
|
-
if (tupleNode?.items) {
|
|
524
|
-
const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix))
|
|
525
|
-
if (namedItems.some((item, i) => item !== tupleNode.items![i])) {
|
|
526
|
-
schemaNode = { ...tupleNode, items: namedItems }
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return ast.createProperty({
|
|
531
|
-
name: propName,
|
|
532
|
-
schema: {
|
|
533
|
-
...schemaNode,
|
|
534
|
-
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
535
|
-
},
|
|
536
|
-
required,
|
|
537
|
-
})
|
|
538
|
-
})
|
|
539
|
-
: []
|
|
540
|
-
|
|
541
|
-
const additionalProperties = schema.additionalProperties
|
|
542
|
-
let additionalPropertiesNode: ast.SchemaNode | boolean | undefined
|
|
543
|
-
if (additionalProperties === true) {
|
|
544
|
-
additionalPropertiesNode = true
|
|
545
|
-
} else if (additionalProperties && Object.keys(additionalProperties).length > 0) {
|
|
546
|
-
additionalPropertiesNode = parseSchema({ schema: additionalProperties as SchemaObject }, rawOptions)
|
|
547
|
-
} else if (additionalProperties === false) {
|
|
548
|
-
additionalPropertiesNode = false
|
|
549
|
-
} else if (additionalProperties) {
|
|
550
|
-
additionalPropertiesNode = ast.createSchema({
|
|
551
|
-
type: typeOptionMap.get(options.unknownType)!,
|
|
552
|
-
})
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const rawPatternProperties = 'patternProperties' in schema ? schema.patternProperties : undefined
|
|
556
|
-
|
|
557
|
-
const patternProperties = rawPatternProperties
|
|
558
|
-
? Object.fromEntries(
|
|
559
|
-
Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [
|
|
560
|
-
pattern,
|
|
561
|
-
patternSchema === true || (typeof patternSchema === 'object' && Object.keys(patternSchema).length === 0)
|
|
562
|
-
? ast.createSchema({
|
|
563
|
-
type: typeOptionMap.get(options.unknownType)!,
|
|
564
|
-
})
|
|
565
|
-
: parseSchema({ schema: patternSchema as SchemaObject }, rawOptions),
|
|
566
|
-
]),
|
|
567
|
-
)
|
|
568
|
-
: undefined
|
|
569
|
-
|
|
570
|
-
const objectNode: ast.SchemaNode = ast.createSchema({
|
|
571
|
-
type: 'object',
|
|
572
|
-
primitive: 'object',
|
|
573
|
-
properties,
|
|
574
|
-
additionalProperties: additionalPropertiesNode,
|
|
575
|
-
patternProperties,
|
|
576
|
-
minProperties: schema.minProperties,
|
|
577
|
-
maxProperties: schema.maxProperties,
|
|
578
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
582
|
-
const discPropName = schema.discriminator.propertyName
|
|
583
|
-
const values = Object.keys(schema.discriminator.mapping)
|
|
584
|
-
const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : undefined
|
|
585
|
-
return ast.setDiscriminatorEnum({
|
|
586
|
-
node: objectNode,
|
|
587
|
-
propertyName: discPropName,
|
|
588
|
-
values,
|
|
589
|
-
enumName,
|
|
590
|
-
})
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return objectNode
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
598
|
-
*/
|
|
599
|
-
function convertTuple({ schema, name, nullable, defaultValue, rawOptions }: SchemaContext): ast.SchemaNode {
|
|
600
|
-
const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item as SchemaObject }, rawOptions))
|
|
601
|
-
const rest = schema.items ? parseSchema({ schema: schema.items as SchemaObject }, rawOptions) : ast.createSchema({ type: 'any' })
|
|
602
|
-
|
|
603
|
-
return ast.createSchema({
|
|
604
|
-
type: 'tuple',
|
|
605
|
-
primitive: 'array',
|
|
606
|
-
items: tupleItems,
|
|
607
|
-
rest,
|
|
608
|
-
min: schema.minItems,
|
|
609
|
-
max: schema.maxItems,
|
|
610
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
611
|
-
})
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Converts a `type: 'array'` schema into an `ArraySchemaNode`.
|
|
616
|
-
*/
|
|
617
|
-
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }: SchemaContext): ast.SchemaNode {
|
|
618
|
-
const rawItems = schema.items as SchemaObject | undefined
|
|
619
|
-
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(undefined, name, options.enumSuffix) : undefined
|
|
620
|
-
const items = rawItems ? [parseSchema({ schema: rawItems, name: itemName }, rawOptions)] : []
|
|
621
|
-
|
|
622
|
-
return ast.createSchema({
|
|
623
|
-
type: 'array',
|
|
624
|
-
primitive: 'array',
|
|
625
|
-
items,
|
|
626
|
-
min: schema.minItems,
|
|
627
|
-
max: schema.maxItems,
|
|
628
|
-
unique: schema.uniqueItems ?? undefined,
|
|
629
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
630
|
-
})
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
635
|
-
*/
|
|
636
|
-
function convertString({ schema, name, nullable, defaultValue }: SchemaContext): ast.SchemaNode {
|
|
637
|
-
return ast.createSchema({
|
|
638
|
-
type: 'string',
|
|
639
|
-
primitive: 'string',
|
|
640
|
-
min: schema.minLength,
|
|
641
|
-
max: schema.maxLength,
|
|
642
|
-
pattern: schema.pattern,
|
|
643
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
644
|
-
})
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Converts a `type: 'number'` or `type: 'integer'` schema.
|
|
649
|
-
*/
|
|
650
|
-
function convertNumeric({ schema, name, nullable, defaultValue }: SchemaContext, type: 'number' | 'integer'): ast.SchemaNode {
|
|
651
|
-
return ast.createSchema({
|
|
652
|
-
type,
|
|
653
|
-
primitive: type,
|
|
654
|
-
min: schema.minimum,
|
|
655
|
-
max: schema.maximum,
|
|
656
|
-
exclusiveMinimum: typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined,
|
|
657
|
-
exclusiveMaximum: typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined,
|
|
658
|
-
multipleOf: schema.multipleOf,
|
|
659
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
660
|
-
})
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Converts a `type: 'boolean'` schema.
|
|
665
|
-
*/
|
|
666
|
-
function convertBoolean({ schema, name, nullable, defaultValue }: SchemaContext): ast.SchemaNode {
|
|
667
|
-
return ast.createSchema({
|
|
668
|
-
type: 'boolean',
|
|
669
|
-
primitive: 'boolean',
|
|
670
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
671
|
-
})
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* Converts an explicit `type: 'null'` schema.
|
|
676
|
-
*/
|
|
677
|
-
function convertNull({ schema, name, nullable }: SchemaContext): ast.SchemaNode {
|
|
678
|
-
return ast.createSchema({
|
|
679
|
-
type: 'null',
|
|
680
|
-
primitive: 'null',
|
|
681
|
-
name,
|
|
682
|
-
title: schema.title,
|
|
683
|
-
description: schema.description,
|
|
684
|
-
deprecated: schema.deprecated,
|
|
685
|
-
nullable,
|
|
686
|
-
})
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
691
|
-
*
|
|
692
|
-
* Dispatch order (first match wins): `$ref` → `allOf` → `oneOf`/`anyOf` → `const` → `format`
|
|
693
|
-
* → octet-stream blob → multi-type array → constraint-inferred type → `enum` → object/array/tuple/scalar
|
|
694
|
-
* → empty-schema fallback (`emptySchemaType` option).
|
|
695
|
-
*/
|
|
696
|
-
function parseSchema({ schema, name }: { schema: SchemaObject; name?: string | null }, rawOptions?: Partial<ast.ParserOptions>): ast.SchemaNode {
|
|
697
|
-
const options: ast.ParserOptions = {
|
|
698
|
-
...DEFAULT_PARSER_OPTIONS,
|
|
699
|
-
...rawOptions,
|
|
700
|
-
}
|
|
701
|
-
const flattenedSchema = flattenSchema(schema)
|
|
702
|
-
if (flattenedSchema && flattenedSchema !== schema) {
|
|
703
|
-
return parseSchema({ schema: flattenedSchema, name }, rawOptions)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const nullable = isNullable(schema) || undefined
|
|
707
|
-
const defaultValue = schema.default === null && nullable ? undefined : schema.default
|
|
708
|
-
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
|
|
709
|
-
|
|
710
|
-
const ctx: SchemaContext = {
|
|
711
|
-
schema,
|
|
712
|
-
name,
|
|
713
|
-
nullable,
|
|
714
|
-
defaultValue,
|
|
715
|
-
type,
|
|
716
|
-
rawOptions,
|
|
717
|
-
options,
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
if (isReference(schema)) return convertRef(ctx)
|
|
721
|
-
|
|
722
|
-
if (schema.allOf?.length) return convertAllOf(ctx)
|
|
723
|
-
const unionMembers = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])]
|
|
724
|
-
if (unionMembers.length) return convertUnion(ctx)
|
|
725
|
-
|
|
726
|
-
if ('const' in schema && schema.const !== undefined) return convertConst(ctx)
|
|
727
|
-
|
|
728
|
-
if (schema.format) {
|
|
729
|
-
const formatResult = convertFormat(ctx)
|
|
730
|
-
if (formatResult) return formatResult
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if (schema.type === 'string' && schema.contentMediaType === 'application/octet-stream') {
|
|
734
|
-
return ast.createSchema({
|
|
735
|
-
type: 'blob',
|
|
736
|
-
primitive: 'string',
|
|
737
|
-
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
738
|
-
})
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
if (Array.isArray(schema.type) && schema.type.length > 1) {
|
|
742
|
-
const nonNullTypes = schema.type.filter((t) => t !== 'null') as string[]
|
|
743
|
-
const arrayNullable = schema.type.includes('null') || nullable || undefined
|
|
744
|
-
|
|
745
|
-
if (nonNullTypes.length > 1) {
|
|
746
|
-
return ast.createSchema({
|
|
747
|
-
type: 'union',
|
|
748
|
-
members: nonNullTypes.map((t) => parseSchema({ schema: { ...schema, type: t } as SchemaObject, name }, rawOptions)),
|
|
749
|
-
...buildSchemaNode(schema, name, arrayNullable, defaultValue),
|
|
750
|
-
})
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (!type) {
|
|
755
|
-
if (schema.minLength !== undefined || schema.maxLength !== undefined || schema.pattern !== undefined) {
|
|
756
|
-
return convertString(ctx)
|
|
757
|
-
}
|
|
758
|
-
if (schema.minimum !== undefined || schema.maximum !== undefined) {
|
|
759
|
-
return convertNumeric(ctx, 'number')
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (schema.enum?.length) return convertEnum(ctx)
|
|
764
|
-
if (type === 'object' || schema.properties || schema.additionalProperties || 'patternProperties' in schema) return convertObject(ctx)
|
|
765
|
-
if ('prefixItems' in schema) return convertTuple(ctx)
|
|
766
|
-
if (type === 'array' || 'items' in schema) return convertArray(ctx)
|
|
767
|
-
if (type === 'string') return convertString(ctx)
|
|
768
|
-
if (type === 'number') return convertNumeric(ctx, 'number')
|
|
769
|
-
if (type === 'integer') return convertNumeric(ctx, 'integer')
|
|
770
|
-
if (type === 'boolean') return convertBoolean(ctx)
|
|
771
|
-
if (type === 'null') return convertNull(ctx)
|
|
772
|
-
|
|
773
|
-
const emptyType = typeOptionMap.get(options.emptySchemaType)!
|
|
774
|
-
return ast.createSchema({
|
|
775
|
-
type: emptyType as ast.ScalarSchemaType,
|
|
776
|
-
name,
|
|
777
|
-
title: schema.title,
|
|
778
|
-
description: schema.description,
|
|
779
|
-
})
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
/**
|
|
783
|
-
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
784
|
-
*/
|
|
785
|
-
function parseParameter(options: ast.ParserOptions, param: Record<string, unknown>): ast.ParameterNode {
|
|
786
|
-
const required = (param['required'] as boolean | undefined) ?? false
|
|
787
|
-
|
|
788
|
-
const schema: ast.SchemaNode = param['schema']
|
|
789
|
-
? parseSchema({ schema: param['schema'] as SchemaObject }, options)
|
|
790
|
-
: ast.createSchema({ type: typeOptionMap.get(options.unknownType)! })
|
|
791
|
-
|
|
792
|
-
return ast.createParameter({
|
|
793
|
-
name: param['name'] as string,
|
|
794
|
-
in: param['in'] as ast.ParameterLocation,
|
|
795
|
-
schema: {
|
|
796
|
-
...schema,
|
|
797
|
-
description: (param['description'] as string | undefined) ?? schema.description,
|
|
798
|
-
},
|
|
799
|
-
required,
|
|
800
|
-
})
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* Reads the inline `requestBody` metadata (description / required) that OAS exposes
|
|
805
|
-
* outside the schema itself. Returns an empty object when the request body is missing or a `$ref`.
|
|
806
|
-
*/
|
|
807
|
-
function getRequestBodyMeta(operation: Operation): {
|
|
808
|
-
description?: string
|
|
809
|
-
required: boolean
|
|
810
|
-
} {
|
|
811
|
-
const body = operation.schema.requestBody as { description?: string; required?: boolean } | undefined
|
|
812
|
-
if (!body) return { required: false }
|
|
813
|
-
|
|
814
|
-
// After getRequestBodyContentTypes has run, body may still carry $ref but the
|
|
815
|
-
// resolved fields (description, required, content) are already spread onto it.
|
|
816
|
-
return {
|
|
817
|
-
description: body.description,
|
|
818
|
-
required: body.required === true,
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/**
|
|
823
|
-
* Reads the inline response object (not a `$ref`) and returns its description plus its `content` map.
|
|
824
|
-
*/
|
|
825
|
-
function getResponseMeta(responseObj: unknown): {
|
|
826
|
-
description?: string
|
|
827
|
-
content?: Record<string, unknown>
|
|
828
|
-
} {
|
|
829
|
-
if (typeof responseObj !== 'object' || responseObj === null || Array.isArray(responseObj)) return {}
|
|
830
|
-
|
|
831
|
-
const inline = responseObj as {
|
|
832
|
-
description?: string
|
|
833
|
-
content?: Record<string, unknown>
|
|
834
|
-
}
|
|
835
|
-
return { description: inline.description, content: inline.content }
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
|
|
840
|
-
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
841
|
-
*/
|
|
842
|
-
function collectPropertyKeysByFlag(schema: SchemaObject | null, flag: 'readOnly' | 'writeOnly'): string[] | undefined {
|
|
843
|
-
if (!schema?.properties) return undefined
|
|
844
|
-
|
|
845
|
-
const keys: string[] = []
|
|
846
|
-
for (const key in schema.properties) {
|
|
847
|
-
const prop = schema.properties[key]
|
|
848
|
-
if (prop && !isReference(prop) && (prop as Record<string, unknown>)[flag]) {
|
|
849
|
-
keys.push(key)
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
return keys.length ? keys : undefined
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
* Converts an OAS `Operation` into an `OperationNode`.
|
|
857
|
-
*/
|
|
858
|
-
function parseOperation(options: ast.ParserOptions, operation: Operation): ast.OperationNode {
|
|
859
|
-
const parameters: Array<ast.ParameterNode> = getParameters(document, operation).map((param) =>
|
|
860
|
-
parseParameter(options, param as unknown as Record<string, unknown>),
|
|
861
|
-
)
|
|
862
|
-
|
|
863
|
-
// Determine which content types to include in requestBody.content.
|
|
864
|
-
// When a global contentType is configured, restrict to that single type.
|
|
865
|
-
// Otherwise include every content type declared in the spec.
|
|
866
|
-
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation)
|
|
867
|
-
|
|
868
|
-
const requestBodyMeta = getRequestBodyMeta(operation)
|
|
869
|
-
|
|
870
|
-
const content = allContentTypes.flatMap((ct) => {
|
|
871
|
-
const schema = getRequestSchema(document, operation, { contentType: ct })
|
|
872
|
-
if (!schema) return []
|
|
873
|
-
return [
|
|
874
|
-
{
|
|
875
|
-
contentType: ct,
|
|
876
|
-
schema: ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
|
|
877
|
-
keysToOmit: collectPropertyKeysByFlag(schema, 'readOnly'),
|
|
878
|
-
},
|
|
879
|
-
]
|
|
880
|
-
})
|
|
881
|
-
|
|
882
|
-
const requestBody =
|
|
883
|
-
content.length > 0 || requestBodyMeta.description
|
|
884
|
-
? {
|
|
885
|
-
description: requestBodyMeta.description,
|
|
886
|
-
required: requestBodyMeta.required || undefined,
|
|
887
|
-
content: content.length > 0 ? content : undefined,
|
|
888
|
-
}
|
|
889
|
-
: undefined
|
|
890
|
-
|
|
891
|
-
const responses: Array<ast.ResponseNode> = operation.getResponseStatusCodes().map((statusCode) => {
|
|
892
|
-
const responseObj = operation.getResponseByStatusCode(statusCode)
|
|
893
|
-
const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType })
|
|
894
|
-
|
|
895
|
-
const schema =
|
|
896
|
-
responseSchema && Object.keys(responseSchema).length > 0
|
|
897
|
-
? parseSchema({ schema: responseSchema }, options)
|
|
898
|
-
: ast.createSchema({
|
|
899
|
-
type: typeOptionMap.get(options.emptySchemaType)!,
|
|
900
|
-
})
|
|
901
|
-
|
|
902
|
-
const { description, content } = getResponseMeta(responseObj)
|
|
903
|
-
const mediaType = content ? getMediaType(Object.keys(content)[0] ?? '') : getMediaType(operation.contentType ?? '')
|
|
904
|
-
|
|
905
|
-
return ast.createResponse({
|
|
906
|
-
statusCode: statusCode as ast.StatusCode,
|
|
907
|
-
description,
|
|
908
|
-
schema,
|
|
909
|
-
mediaType,
|
|
910
|
-
keysToOmit: collectPropertyKeysByFlag(responseSchema, 'writeOnly'),
|
|
911
|
-
})
|
|
912
|
-
})
|
|
913
|
-
|
|
914
|
-
const urlPath = new URLPath(operation.path)
|
|
915
|
-
|
|
916
|
-
return ast.createOperation({
|
|
917
|
-
operationId: operation.getOperationId(),
|
|
918
|
-
method: operation.method.toUpperCase() as ast.HttpMethod,
|
|
919
|
-
path: urlPath.path,
|
|
920
|
-
tags: operation.getTags().map((tag) => tag.name),
|
|
921
|
-
summary: operation.getSummary() || undefined,
|
|
922
|
-
description: operation.getDescription() || undefined,
|
|
923
|
-
deprecated: operation.isDeprecated() || undefined,
|
|
924
|
-
parameters,
|
|
925
|
-
requestBody,
|
|
926
|
-
responses,
|
|
927
|
-
})
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
return { parseSchema, parseOperation, parseParameter }
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
/**
|
|
934
|
-
* Parses a single OpenAPI `SchemaObject` into a `SchemaNode`.
|
|
935
|
-
*
|
|
936
|
-
* Use this for targeted schema parsing when you don't need the full spec.
|
|
937
|
-
* For complete spec parsing, use `parseOas()` instead which handles operations and all schemas together.
|
|
938
|
-
*
|
|
939
|
-
* @note Circular schema references are tracked via internal state and resolve appropriately.
|
|
940
|
-
*
|
|
941
|
-
* @example
|
|
942
|
-
* ```ts
|
|
943
|
-
* const document = yaml.parse(fs.readFileSync('openapi.yaml', 'utf8'))
|
|
944
|
-
* const ctx = { document }
|
|
945
|
-
* const schema = parseSchema(ctx, { schema: { type: 'string', format: 'uuid' } })
|
|
946
|
-
* ```
|
|
947
|
-
*/
|
|
948
|
-
export function parseSchema(
|
|
949
|
-
ctx: OasParserContext,
|
|
950
|
-
{ schema, name }: { schema: SchemaObject; name?: string },
|
|
951
|
-
options?: Partial<ast.ParserOptions>,
|
|
952
|
-
): ast.SchemaNode {
|
|
953
|
-
return createSchemaParser(ctx).parseSchema({ schema, name }, options)
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/**
|
|
957
|
-
* Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
|
|
958
|
-
*
|
|
959
|
-
* This is the main entry point for `@kubb/adapter-oas`. It converts OpenAPI/Swagger specs into a spec-agnostic tree
|
|
960
|
-
* that downstream plugins (`plugin-ts`, `plugin-zod`, etc.) consume for code generation. No code is generated here —
|
|
961
|
-
* the tree is a pure data structure representing all schemas and operations.
|
|
962
|
-
*
|
|
963
|
-
* Returns the AST root and a `nameMapping` for resolving schema references.
|
|
964
|
-
*
|
|
965
|
-
* @example
|
|
966
|
-
* ```ts
|
|
967
|
-
* import { parseOas } from '@kubb/adapter-oas'
|
|
968
|
-
*
|
|
969
|
-
* const document = await parseFromConfig(config)
|
|
970
|
-
* const { root, nameMapping } = parseOas(document, { dateType: 'date', contentType: 'application/json' })
|
|
971
|
-
* ```
|
|
972
|
-
*/
|
|
973
|
-
export function parseOas(
|
|
974
|
-
document: Document,
|
|
975
|
-
options: Partial<ast.ParserOptions> & { contentType?: ContentType } = {},
|
|
976
|
-
): { root: ast.InputNode; nameMapping: Map<string, string> } {
|
|
977
|
-
const { contentType, ...parserOptions } = options
|
|
978
|
-
const mergedOptions: ast.ParserOptions = {
|
|
979
|
-
...DEFAULT_PARSER_OPTIONS,
|
|
980
|
-
...parserOptions,
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
const { schemas: schemaObjects, nameMapping } = getSchemas(document, {
|
|
984
|
-
contentType,
|
|
985
|
-
})
|
|
986
|
-
const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({ document, contentType })
|
|
987
|
-
|
|
988
|
-
const schemas: Array<ast.SchemaNode> = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({ schema, name }, mergedOptions))
|
|
989
|
-
|
|
990
|
-
const baseOas = new BaseOas(document)
|
|
991
|
-
const paths = baseOas.getPaths()
|
|
992
|
-
|
|
993
|
-
const operations: Array<ast.OperationNode> = Object.entries(paths).flatMap(([_path, methods]) =>
|
|
994
|
-
Object.entries(methods)
|
|
995
|
-
.map(([, operation]) => (operation ? _parseOperation(mergedOptions, operation) : null))
|
|
996
|
-
.filter((op): op is ast.OperationNode => op !== null),
|
|
997
|
-
)
|
|
998
|
-
|
|
999
|
-
const root = ast.createInput({ schemas, operations })
|
|
1000
|
-
|
|
1001
|
-
return { root, nameMapping }
|
|
1002
|
-
}
|