@kubb/oas 4.34.0 → 4.35.1
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 +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/parser.ts +0 -1163
package/dist/index.cjs
CHANGED
|
@@ -948,6 +948,7 @@ exports.KNOWN_MEDIA_TYPES = KNOWN_MEDIA_TYPES;
|
|
|
948
948
|
exports.KUBB_INLINE_REF_PREFIX = KUBB_INLINE_REF_PREFIX;
|
|
949
949
|
exports.Oas = Oas;
|
|
950
950
|
exports.STRUCTURAL_KEYS = STRUCTURAL_KEYS;
|
|
951
|
+
exports.flattenSchema = flattenSchema;
|
|
951
952
|
exports.getDefaultValue = getDefaultValue;
|
|
952
953
|
exports.httpMethods = httpMethods;
|
|
953
954
|
exports.isAllOptional = isAllOptional;
|
package/dist/index.d.ts
CHANGED
|
@@ -220,10 +220,15 @@ declare function merge(pathOrApi: Array<string | Document>, {
|
|
|
220
220
|
oasClass?: typeof Oas;
|
|
221
221
|
}): Promise<Oas>;
|
|
222
222
|
declare function parseFromConfig(config: Config, oasClass?: typeof Oas): Promise<Oas>;
|
|
223
|
+
/**
|
|
224
|
+
* Flatten allOf schemas by merging keyword-only fragments.
|
|
225
|
+
* Only flattens schemas where allOf items don't contain structural keys or $refs.
|
|
226
|
+
*/
|
|
227
|
+
declare function flattenSchema(schema: SchemaObject | null): SchemaObject | null;
|
|
223
228
|
/**
|
|
224
229
|
* Validate an OpenAPI document using oas-normalize.
|
|
225
230
|
*/
|
|
226
231
|
declare function validate(document: Document): Promise<oas_normalize_lib_types0.ValidationResult>;
|
|
227
232
|
//#endregion
|
|
228
|
-
export { DiscriminatorObject, Document, ENUM_EXTENSION_KEYS, FORMAT_MAP, HttpMethod, httpMethods as HttpMethods, httpMethods, KNOWN_MEDIA_TYPES, KUBB_INLINE_REF_PREFIX, MediaTypeObject, Oas, type OasTypes, type OpenAPIV3, type OpenAPIV3_1, Operation, ReferenceObject, ResponseObject, STRUCTURAL_KEYS, SchemaObject, contentType, getDefaultValue, isAllOptional, isDiscriminator, isNullable, isOpenApiV3_1Document, isOptional, isParameterObject, isReference, isRequired, merge, parse, parseFromConfig, resolveServerUrl, validate };
|
|
233
|
+
export { DiscriminatorObject, Document, ENUM_EXTENSION_KEYS, FORMAT_MAP, HttpMethod, httpMethods as HttpMethods, httpMethods, KNOWN_MEDIA_TYPES, KUBB_INLINE_REF_PREFIX, MediaTypeObject, Oas, type OasTypes, type OpenAPIV3, type OpenAPIV3_1, Operation, ReferenceObject, ResponseObject, STRUCTURAL_KEYS, SchemaObject, contentType, flattenSchema, getDefaultValue, isAllOptional, isDiscriminator, isNullable, isOpenApiV3_1Document, isOptional, isParameterObject, isReference, isRequired, merge, parse, parseFromConfig, resolveServerUrl, validate };
|
|
229
234
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -913,6 +913,6 @@ function resolveServerUrl(server, overrides) {
|
|
|
913
913
|
return url;
|
|
914
914
|
}
|
|
915
915
|
//#endregion
|
|
916
|
-
export { ENUM_EXTENSION_KEYS, FORMAT_MAP, httpMethods as HttpMethods, httpMethods, KNOWN_MEDIA_TYPES, KUBB_INLINE_REF_PREFIX, Oas, STRUCTURAL_KEYS, getDefaultValue, isAllOptional, isDiscriminator, isNullable, isOpenApiV3_1Document, isOptional, isParameterObject, isReference, isRequired, merge, parse, parseFromConfig, resolveServerUrl, validate };
|
|
916
|
+
export { ENUM_EXTENSION_KEYS, FORMAT_MAP, httpMethods as HttpMethods, httpMethods, KNOWN_MEDIA_TYPES, KUBB_INLINE_REF_PREFIX, Oas, STRUCTURAL_KEYS, flattenSchema, getDefaultValue, isAllOptional, isDiscriminator, isNullable, isOpenApiV3_1Document, isOptional, isParameterObject, isReference, isRequired, merge, parse, parseFromConfig, resolveServerUrl, validate };
|
|
917
917
|
|
|
918
918
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/oas",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.35.1",
|
|
4
4
|
"description": "OpenAPI Specification (OAS) utilities and helpers for Kubb, providing parsing, normalization, and manipulation of OpenAPI/Swagger schemas.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openapi",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"openapi-types": "^12.1.3",
|
|
62
62
|
"remeda": "^2.33.6",
|
|
63
63
|
"swagger2openapi": "^7.0.8",
|
|
64
|
-
"@kubb/ast": "4.
|
|
65
|
-
"@kubb/core": "4.
|
|
64
|
+
"@kubb/ast": "4.35.1",
|
|
65
|
+
"@kubb/core": "4.35.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/swagger2openapi": "^7.0.4",
|
package/src/index.ts
CHANGED
package/src/parser.ts
DELETED
|
@@ -1,1163 +0,0 @@
|
|
|
1
|
-
import { pascalCase } from '@internals/utils'
|
|
2
|
-
import {
|
|
3
|
-
collect,
|
|
4
|
-
createOperation,
|
|
5
|
-
createParameter,
|
|
6
|
-
createProperty,
|
|
7
|
-
createResponse,
|
|
8
|
-
createRoot,
|
|
9
|
-
createSchema,
|
|
10
|
-
narrowSchema,
|
|
11
|
-
schemaTypes,
|
|
12
|
-
transform,
|
|
13
|
-
} from '@kubb/ast'
|
|
14
|
-
import type {
|
|
15
|
-
ArraySchemaNode,
|
|
16
|
-
DateSchemaNode,
|
|
17
|
-
DatetimeSchemaNode,
|
|
18
|
-
EnumSchemaNode,
|
|
19
|
-
HttpMethod,
|
|
20
|
-
IntersectionSchemaNode,
|
|
21
|
-
MediaType,
|
|
22
|
-
NumberSchemaNode,
|
|
23
|
-
ObjectSchemaNode,
|
|
24
|
-
OperationNode,
|
|
25
|
-
ParameterLocation,
|
|
26
|
-
ParameterNode,
|
|
27
|
-
PrimitiveSchemaType,
|
|
28
|
-
PropertyNode,
|
|
29
|
-
RefSchemaNode,
|
|
30
|
-
ResponseNode,
|
|
31
|
-
RootNode,
|
|
32
|
-
ScalarSchemaNode,
|
|
33
|
-
ScalarSchemaType,
|
|
34
|
-
SchemaNode,
|
|
35
|
-
SchemaType,
|
|
36
|
-
StatusCode,
|
|
37
|
-
StringSchemaNode,
|
|
38
|
-
TimeSchemaNode,
|
|
39
|
-
UnionSchemaNode,
|
|
40
|
-
} from '@kubb/ast/types'
|
|
41
|
-
import type { KubbFile } from '@kubb/fabric-core/types'
|
|
42
|
-
import { ENUM_EXTENSION_KEYS, FORMAT_MAP, KNOWN_MEDIA_TYPES } from './constants.ts'
|
|
43
|
-
import type { Oas } from './Oas.ts'
|
|
44
|
-
import type { contentType, Operation, SchemaObject } from './types.ts'
|
|
45
|
-
import { flattenSchema, isDiscriminator, isNullable, isReference } from './utils.ts'
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Distributive `Omit` — correctly distributes over union types so that
|
|
49
|
-
* `Omit<A | B, 'kind'>` produces `Omit<A, 'kind'> | Omit<B, 'kind'>`
|
|
50
|
-
* rather than `Omit<A | B, 'kind'>`.
|
|
51
|
-
*/
|
|
52
|
-
type DistributiveOmit<TValue, TKey extends PropertyKey> = TValue extends unknown ? Omit<TValue, TKey> : never
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Maps each `dateType` option value to the AST node produced by `format: 'date-time'`.
|
|
56
|
-
*/
|
|
57
|
-
type DateTimeNodeByDateType = {
|
|
58
|
-
date: DateSchemaNode
|
|
59
|
-
string: DatetimeSchemaNode
|
|
60
|
-
stringOffset: DatetimeSchemaNode
|
|
61
|
-
stringLocal: DatetimeSchemaNode
|
|
62
|
-
false: StringSchemaNode
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Resolves the AST node produced by `format: 'date-time'` based on the `dateType` option.
|
|
67
|
-
*/
|
|
68
|
-
type ResolveDateTimeNode<TDateType extends Options['dateType']> = DateTimeNodeByDateType[TDateType extends keyof DateTimeNodeByDateType ? TDateType : 'string']
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Single source of truth: ordered list of `[shape, SchemaNode]` pairs.
|
|
72
|
-
* `InferSchemaNode` walks this tuple in order and returns the node type of the first matching entry.
|
|
73
|
-
* Parameterized over `TDateType` so `format: 'date-time'` resolves to the correct node based on the option.
|
|
74
|
-
*/
|
|
75
|
-
type SchemaNodeMap<TDateType extends Options['dateType'] = Options['dateType']> = [
|
|
76
|
-
[{ $ref: string }, RefSchemaNode],
|
|
77
|
-
// allOf with sibling `properties` always produces an intersection (shared props are appended as a member).
|
|
78
|
-
[{ allOf: ReadonlyArray<unknown>; properties: object }, IntersectionSchemaNode],
|
|
79
|
-
// allOf with 2+ members always produces an intersection.
|
|
80
|
-
[{ allOf: readonly [unknown, unknown, ...unknown[]] }, IntersectionSchemaNode],
|
|
81
|
-
// Single-member allOf without sibling `properties` flattens to the member type.
|
|
82
|
-
[{ allOf: ReadonlyArray<unknown> }, SchemaNode],
|
|
83
|
-
[{ oneOf: ReadonlyArray<unknown> }, UnionSchemaNode],
|
|
84
|
-
[{ anyOf: ReadonlyArray<unknown> }, UnionSchemaNode],
|
|
85
|
-
[{ const: null }, ScalarSchemaNode],
|
|
86
|
-
[{ const: string | number | boolean }, EnumSchemaNode],
|
|
87
|
-
// OAS 3.1 multi-type array: `{ type: ['string', 'integer'] }` → union node.
|
|
88
|
-
[{ type: ReadonlyArray<string> }, UnionSchemaNode],
|
|
89
|
-
// `{ type: 'array', enum }` is normalized at runtime: enum moves into items → array node.
|
|
90
|
-
[{ type: 'array'; enum: ReadonlyArray<unknown> }, ArraySchemaNode],
|
|
91
|
-
[{ enum: ReadonlyArray<unknown> }, EnumSchemaNode],
|
|
92
|
-
[{ type: 'object' }, ObjectSchemaNode],
|
|
93
|
-
[{ additionalProperties: boolean | {} }, ObjectSchemaNode],
|
|
94
|
-
[{ type: 'array' }, ArraySchemaNode],
|
|
95
|
-
[{ items: object }, ArraySchemaNode],
|
|
96
|
-
[{ prefixItems: ReadonlyArray<unknown> }, ArraySchemaNode],
|
|
97
|
-
// Format entries with explicit type — placed before generic type entries so format wins.
|
|
98
|
-
[{ type: string; format: 'date-time' }, ResolveDateTimeNode<TDateType>],
|
|
99
|
-
[{ type: string; format: 'date' }, DateSchemaNode],
|
|
100
|
-
[{ type: string; format: 'time' }, TimeSchemaNode],
|
|
101
|
-
[{ format: 'date-time' }, ResolveDateTimeNode<TDateType>],
|
|
102
|
-
[{ format: 'date' }, DateSchemaNode],
|
|
103
|
-
[{ format: 'time' }, TimeSchemaNode],
|
|
104
|
-
[{ type: 'string' }, StringSchemaNode],
|
|
105
|
-
[{ type: 'number' }, NumberSchemaNode],
|
|
106
|
-
[{ type: 'integer' }, NumberSchemaNode],
|
|
107
|
-
[{ type: 'bigint' }, NumberSchemaNode],
|
|
108
|
-
[{ type: string }, ScalarSchemaNode],
|
|
109
|
-
// Inferred scalar types from constraints when no explicit type is present.
|
|
110
|
-
[{ minLength: number }, StringSchemaNode],
|
|
111
|
-
[{ maxLength: number }, StringSchemaNode],
|
|
112
|
-
[{ pattern: string }, StringSchemaNode],
|
|
113
|
-
[{ minimum: number }, NumberSchemaNode],
|
|
114
|
-
[{ maximum: number }, NumberSchemaNode],
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
export type InferSchemaNode<
|
|
118
|
-
TSchema extends SchemaObject,
|
|
119
|
-
TDateType extends Options['dateType'] = Options['dateType'],
|
|
120
|
-
TEntries extends ReadonlyArray<[object, SchemaNode]> = SchemaNodeMap<TDateType>,
|
|
121
|
-
> = TEntries extends [infer TEntry extends [object, SchemaNode], ...infer TRest extends ReadonlyArray<[object, SchemaNode]>]
|
|
122
|
-
? TSchema extends TEntry[0]
|
|
123
|
-
? TEntry[1]
|
|
124
|
-
: InferSchemaNode<TSchema, TDateType, TRest>
|
|
125
|
-
: SchemaNode
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Controls how various OAS constructs are mapped to Kubb AST nodes.
|
|
129
|
-
*/
|
|
130
|
-
export type Options = {
|
|
131
|
-
/**
|
|
132
|
-
* How `format: 'date-time'` schemas are represented. `false` falls through to a plain string.
|
|
133
|
-
*/
|
|
134
|
-
dateType: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
|
|
135
|
-
/**
|
|
136
|
-
* Whether `type: 'integer'` and `format: 'int64'` produce `number` or `bigint` nodes.
|
|
137
|
-
*/
|
|
138
|
-
integerType?: 'number' | 'bigint'
|
|
139
|
-
/**
|
|
140
|
-
* AST type used when no schema type can be inferred.
|
|
141
|
-
*/
|
|
142
|
-
unknownType: 'any' | 'unknown' | 'void'
|
|
143
|
-
/**
|
|
144
|
-
* AST type used for completely empty schemas (`{}`).
|
|
145
|
-
*/
|
|
146
|
-
emptySchemaType: 'any' | 'unknown' | 'void'
|
|
147
|
-
/**
|
|
148
|
-
* Suffix appended to derived enum names when building property schema names.
|
|
149
|
-
*/
|
|
150
|
-
enumSuffix: string
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Construction-time options for `createOasParser`.
|
|
155
|
-
*/
|
|
156
|
-
type OasParserOptions = {
|
|
157
|
-
contentType?: contentType
|
|
158
|
-
collisionDetection?: boolean
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Default values for all `Options` fields.
|
|
163
|
-
*/
|
|
164
|
-
const DEFAULT_OPTIONS = {
|
|
165
|
-
dateType: 'string',
|
|
166
|
-
integerType: 'number',
|
|
167
|
-
unknownType: 'any',
|
|
168
|
-
emptySchemaType: 'any',
|
|
169
|
-
enumSuffix: 'enum',
|
|
170
|
-
} as const satisfies Options
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Looks up the Kubb `SchemaType` for a given OAS `format` string.
|
|
174
|
-
* Returns `undefined` for formats not in `FORMAT_MAP` (e.g. `int64`, `date-time`),
|
|
175
|
-
* which are handled separately because their output depends on parser options.
|
|
176
|
-
*/
|
|
177
|
-
function formatToSchemaType(format: string): SchemaType | undefined {
|
|
178
|
-
return FORMAT_MAP[format as keyof typeof FORMAT_MAP]
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Extracts the final path segment of a JSON Pointer `$ref` string.
|
|
183
|
-
* For `#/components/schemas/Order` this returns `'Order'`.
|
|
184
|
-
* Falls back to the full ref string when no slash is present.
|
|
185
|
-
*/
|
|
186
|
-
function extractRefName($ref: string): string {
|
|
187
|
-
return $ref.split('/').at(-1) ?? $ref
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
192
|
-
* Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
|
|
193
|
-
* `boolean` maps to `'boolean'`; everything else defaults to `'string'`.
|
|
194
|
-
*/
|
|
195
|
-
function getPrimitiveType(type: string | undefined): PrimitiveSchemaType {
|
|
196
|
-
if (type === 'number' || type === 'integer' || type === 'bigint') return type
|
|
197
|
-
if (type === 'boolean') return 'boolean'
|
|
198
|
-
return 'string'
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Narrows a raw content-type string to the `MediaType` union recognized by Kubb.
|
|
203
|
-
* Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
|
|
204
|
-
*/
|
|
205
|
-
function toMediaType(contentType: string): MediaType | undefined {
|
|
206
|
-
return KNOWN_MEDIA_TYPES.includes(contentType as MediaType) ? (contentType as MediaType) : undefined
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Pre-computed per-schema context passed to every `convert*` branch handler.
|
|
211
|
-
* Grouping these values avoids repeating the same derivations across all branches.
|
|
212
|
-
*/
|
|
213
|
-
type SchemaContext = {
|
|
214
|
-
schema: SchemaObject
|
|
215
|
-
name: string | undefined
|
|
216
|
-
nullable: true | undefined
|
|
217
|
-
defaultValue: unknown
|
|
218
|
-
/**
|
|
219
|
-
* Normalized single type string (first element when OAS 3.1 multi-type array).
|
|
220
|
-
*/
|
|
221
|
-
type: string | undefined
|
|
222
|
-
options: Partial<Options> | undefined
|
|
223
|
-
mergedOptions: Options
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* The public interface returned by `createOasParser`.
|
|
228
|
-
*/
|
|
229
|
-
export type OasParser = {
|
|
230
|
-
/**
|
|
231
|
-
* Converts an OpenAPI/Swagger spec (wrapped in a Kubb `Oas` instance) into
|
|
232
|
-
* a `RootNode` — the top-level node of the `@kubb/ast` tree.
|
|
233
|
-
*/
|
|
234
|
-
buildAst: <TOptions extends Partial<Options> = object>(options?: TOptions) => RootNode
|
|
235
|
-
convertSchema: <TFormat extends string, TSchema extends SchemaObject & { format?: TFormat }, TOptions extends Partial<Options> = object>(
|
|
236
|
-
params: { schema: TSchema; name?: string },
|
|
237
|
-
options?: TOptions,
|
|
238
|
-
) => InferSchemaNode<TSchema, TOptions extends { dateType: Options['dateType'] } ? TOptions['dateType'] : (typeof DEFAULT_OPTIONS)['dateType']>
|
|
239
|
-
/**
|
|
240
|
-
* Walks `node` and replaces each `ref` value with the name returned by
|
|
241
|
-
* `resolveName`. The callback receives the full `$ref` path (e.g. `#/components/schemas/Order`)
|
|
242
|
-
* when available, falling back to the short name. Pass a no-op (`(n) => n`) to skip resolution.
|
|
243
|
-
*
|
|
244
|
-
* The optional `resolveEnumName` callback is called for inline `enum` nodes and should return
|
|
245
|
-
* the transformed name to use (e.g. with a plugin `transformers.name` applied).
|
|
246
|
-
*/
|
|
247
|
-
resolveRefs: (node: SchemaNode, resolveName: (ref: string) => string | undefined, resolveEnumName?: (name: string) => string | undefined) => SchemaNode
|
|
248
|
-
/**
|
|
249
|
-
* Extracts `KubbFile.Import` entries from a `SchemaNode` tree by collecting
|
|
250
|
-
* all importable `ref` nodes. A `$ref` is considered importable when it resolves
|
|
251
|
-
* to a known component in the OAS spec (`oas.get($ref)` is truthy).
|
|
252
|
-
*
|
|
253
|
-
* The `resolve` callback is called with the schema name (last segment of the
|
|
254
|
-
* `$ref`, collision-corrected via the OAS name mapping) and must return the
|
|
255
|
-
* `{ name, path }` pair for the generated import, or `undefined` to skip it.
|
|
256
|
-
*
|
|
257
|
-
* @example
|
|
258
|
-
* ```ts
|
|
259
|
-
* const imports = parser.getImports(schemaNode, (schemaName) => ({
|
|
260
|
-
* name: schemaManager.getName(schemaName, { type: 'type' }),
|
|
261
|
-
* path: schemaManager.getFile(schemaName).path,
|
|
262
|
-
* }))
|
|
263
|
-
* ```
|
|
264
|
-
*/
|
|
265
|
-
getImports: (node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string } | undefined) => Array<KubbFile.Import>
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Creates an OAS parser that converts an OpenAPI/Swagger spec into
|
|
270
|
-
* the `@kubb/ast` tree.
|
|
271
|
-
*
|
|
272
|
-
* Options are passed per-call to `buildAst` or `convertSchema` rather than
|
|
273
|
-
* at construction time, keeping the factory lightweight.
|
|
274
|
-
*
|
|
275
|
-
* This is the **kubb-parser** stage of the compilation lifecycle:
|
|
276
|
-
* OpenAPI / Swagger → Kubb AST
|
|
277
|
-
*
|
|
278
|
-
* No code is generated here; the resulting tree is spec-agnostic and can
|
|
279
|
-
* be consumed by any downstream plugin (plugin-ts, plugin-zod, …).
|
|
280
|
-
*
|
|
281
|
-
* @example
|
|
282
|
-
* ```ts
|
|
283
|
-
* const parser = createOasParser(oas)
|
|
284
|
-
* const root = parser.buildAst({ emptySchemaType: 'unknown' })
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
export function createOasParser(oas: Oas, { contentType, collisionDetection }: OasParserOptions = {}): OasParser {
|
|
288
|
-
// Map from original component paths to resolved schema names (after collision resolution)
|
|
289
|
-
// e.g., { '#/components/schemas/Order': 'OrderSchema', '#/components/responses/Product': 'ProductResponse' }
|
|
290
|
-
const { schemas: schemaObjects, nameMapping } = oas.getSchemas({ contentType, collisionDetection })
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Resolves the `schemaTypes` constant for the `unknownType` option value.
|
|
294
|
-
* Used when a schema has no inferrable type (e.g. empty `additionalProperties`).
|
|
295
|
-
*/
|
|
296
|
-
function getUnknownType(options: Options) {
|
|
297
|
-
if (options.unknownType === 'any') {
|
|
298
|
-
return schemaTypes.any
|
|
299
|
-
}
|
|
300
|
-
if (options.unknownType === 'void') {
|
|
301
|
-
return schemaTypes.void
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return schemaTypes.unknown
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Resolves the `schemaTypes` constant for the `emptySchemaType` option value.
|
|
308
|
-
* Used as the fallback type for completely empty schemas (`{}`).
|
|
309
|
-
*/
|
|
310
|
-
function getEmptySchemaType(options: Options) {
|
|
311
|
-
if (options.emptySchemaType === 'any') {
|
|
312
|
-
return schemaTypes.any
|
|
313
|
-
}
|
|
314
|
-
if (options.emptySchemaType === 'void') {
|
|
315
|
-
return schemaTypes.void
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return schemaTypes.unknown
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Resolves the AST type and datetime modifiers for a date/time format, honoring the `dateType` option.
|
|
323
|
-
* Returns `undefined` when `dateType` is `false`, meaning the format should fall through to `string`.
|
|
324
|
-
*/
|
|
325
|
-
function getDateType(
|
|
326
|
-
options: Options,
|
|
327
|
-
format: 'date-time' | 'date' | 'time',
|
|
328
|
-
): { type: 'datetime'; offset?: boolean; local?: boolean } | { type: 'date' | 'time'; representation: 'date' | 'string' } | undefined {
|
|
329
|
-
if (!options.dateType) {
|
|
330
|
-
return undefined
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (format === 'date-time') {
|
|
334
|
-
if (options.dateType === 'date') {
|
|
335
|
-
return { type: 'date', representation: 'date' }
|
|
336
|
-
}
|
|
337
|
-
if (options.dateType === 'stringOffset') {
|
|
338
|
-
return { type: 'datetime', offset: true }
|
|
339
|
-
}
|
|
340
|
-
if (options.dateType === 'stringLocal') {
|
|
341
|
-
return { type: 'datetime', local: true }
|
|
342
|
-
}
|
|
343
|
-
return { type: 'datetime', offset: false }
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (format === 'date') {
|
|
347
|
-
return { type: 'date', representation: options.dateType === 'date' ? 'date' : 'string' }
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// time
|
|
351
|
-
return { type: 'time', representation: options.dateType === 'date' ? 'date' : 'string' }
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Shared metadata fields included in every `createSchema` call.
|
|
356
|
-
* Centralizes the common properties so sub-handlers don't repeat them.
|
|
357
|
-
*/
|
|
358
|
-
function buildSchemaBase(schema: SchemaObject, name: string | undefined, nullable: true | undefined, defaultValue: unknown) {
|
|
359
|
-
return {
|
|
360
|
-
name,
|
|
361
|
-
nullable,
|
|
362
|
-
title: schema.title,
|
|
363
|
-
description: schema.description,
|
|
364
|
-
deprecated: schema.deprecated,
|
|
365
|
-
readOnly: schema.readOnly,
|
|
366
|
-
writeOnly: schema.writeOnly,
|
|
367
|
-
default: defaultValue,
|
|
368
|
-
example: schema.example,
|
|
369
|
-
} as const
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Branch handlers — each converts one OAS schema pattern to a SchemaNode.
|
|
373
|
-
// They are defined as function declarations so they can reference each other
|
|
374
|
-
// and `convertSchema` freely (JS hoisting).
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Converts a `$ref` schema pointer into a `RefSchemaNode`.
|
|
378
|
-
*
|
|
379
|
-
* In OAS 3.0 siblings of `$ref` are technically ignored by the spec, but Kubb intentionally
|
|
380
|
-
* preserves them so that annotations like `pattern`, `description`, and `nullable` are
|
|
381
|
-
* reflected in generated JSDoc and type modifiers.
|
|
382
|
-
*/
|
|
383
|
-
function convertRef({ schema, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
384
|
-
const schemaObject = schema as unknown as SchemaObject & { $ref: string }
|
|
385
|
-
return createSchema({
|
|
386
|
-
type: 'ref',
|
|
387
|
-
name: extractRefName(schemaObject.$ref),
|
|
388
|
-
ref: schemaObject.$ref,
|
|
389
|
-
nullable,
|
|
390
|
-
description: schemaObject.description,
|
|
391
|
-
deprecated: schemaObject.deprecated,
|
|
392
|
-
readOnly: schemaObject.readOnly,
|
|
393
|
-
writeOnly: schemaObject.writeOnly,
|
|
394
|
-
pattern: schemaObject.type === 'string' ? schemaObject.pattern : undefined,
|
|
395
|
-
example: schemaObject.example,
|
|
396
|
-
default: defaultValue,
|
|
397
|
-
})
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Converts a `allOf` schema into either a flattened member node (single-member `allOf`)
|
|
402
|
-
* or an `IntersectionSchemaNode` (multi-member `allOf`).
|
|
403
|
-
*
|
|
404
|
-
* Single-member `allOf` without sibling structural keys is the common OAS 3.0 pattern for
|
|
405
|
-
* annotating a `$ref` or primitive with extra constraints; it is flattened to avoid
|
|
406
|
-
* producing needless intersection wrappers.
|
|
407
|
-
*
|
|
408
|
-
* The flatten path is skipped when the outer schema carries structural keys that cannot be
|
|
409
|
-
* merged into annotation fields: `properties`, `required`, or `additionalProperties`.
|
|
410
|
-
* Those cases must become an intersection so the constraints are preserved.
|
|
411
|
-
*
|
|
412
|
-
* Circular references through discriminator parents are detected and skipped to prevent
|
|
413
|
-
* infinite recursion during code generation.
|
|
414
|
-
*/
|
|
415
|
-
function convertAllOf({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
416
|
-
if (
|
|
417
|
-
schema.allOf!.length === 1 &&
|
|
418
|
-
!schema.properties &&
|
|
419
|
-
!(Array.isArray(schema.required) && schema.required.length) &&
|
|
420
|
-
schema.additionalProperties === undefined
|
|
421
|
-
) {
|
|
422
|
-
const [memberSchema] = schema.allOf as SchemaObject[]
|
|
423
|
-
const memberNode = convertSchema({ schema: memberSchema! }, options)
|
|
424
|
-
const { kind: _kind, ...memberNodeProps } = memberNode
|
|
425
|
-
const mergedNullable = nullable || memberNode.nullable || undefined
|
|
426
|
-
const mergedDefault = schema.default === null && mergedNullable ? undefined : (schema.default ?? memberNode.default)
|
|
427
|
-
|
|
428
|
-
return createSchema({
|
|
429
|
-
...memberNodeProps,
|
|
430
|
-
name,
|
|
431
|
-
title: schema.title ?? memberNode.title,
|
|
432
|
-
description: schema.description ?? memberNode.description,
|
|
433
|
-
deprecated: schema.deprecated ?? memberNode.deprecated,
|
|
434
|
-
nullable: mergedNullable,
|
|
435
|
-
readOnly: schema.readOnly ?? memberNode.readOnly,
|
|
436
|
-
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
437
|
-
default: mergedDefault,
|
|
438
|
-
example: schema.example ?? memberNode.example,
|
|
439
|
-
pattern: schema.pattern ?? ('pattern' in memberNode ? memberNode.pattern : undefined),
|
|
440
|
-
} as DistributiveOmit<SchemaNode, 'kind'>)
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// When a child schema extends a discriminator parent via allOf and the parent's oneOf/anyOf
|
|
444
|
-
// references that child back, skip that allOf item to prevent a circular type reference.
|
|
445
|
-
const allOfMembers: SchemaNode[] = (schema.allOf as SchemaObject[])
|
|
446
|
-
.filter((item) => {
|
|
447
|
-
if (!isReference(item) || !name) return true
|
|
448
|
-
const deref = oas.get<SchemaObject>((item as { $ref: string }).$ref)
|
|
449
|
-
if (!deref || !isDiscriminator(deref)) return true
|
|
450
|
-
const parentUnion = (deref as SchemaObject).oneOf ?? (deref as SchemaObject).anyOf
|
|
451
|
-
if (!parentUnion) return true
|
|
452
|
-
const childRef = `#/components/schemas/${name}`
|
|
453
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && (oneOfItem as { $ref: string }).$ref === childRef)
|
|
454
|
-
const inMapping = Object.values((deref as SchemaObject & { discriminator: { mapping?: Record<string, string> } }).discriminator.mapping ?? {}).some(
|
|
455
|
-
(v) => v === childRef,
|
|
456
|
-
)
|
|
457
|
-
return !inOneOf && !inMapping
|
|
458
|
-
})
|
|
459
|
-
.map((s) => convertSchema({ schema: s as SchemaObject }, options))
|
|
460
|
-
|
|
461
|
-
// When `required` lists keys not present in the outer `properties`, resolve them from
|
|
462
|
-
// the allOf member schemas and inject them as extra intersection members.
|
|
463
|
-
if (Array.isArray(schema.required) && schema.required.length) {
|
|
464
|
-
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : new Set<string>()
|
|
465
|
-
const missingRequired = schema.required.filter((key) => !outerKeys.has(key))
|
|
466
|
-
|
|
467
|
-
if (missingRequired.length) {
|
|
468
|
-
const resolvedMembers = (schema.allOf as SchemaObject[]).flatMap((item) => {
|
|
469
|
-
if (!isReference(item)) return [item]
|
|
470
|
-
const deref = oas.get<SchemaObject>(item.$ref)
|
|
471
|
-
return deref && !isReference(deref) ? [deref as SchemaObject] : []
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
for (const key of missingRequired) {
|
|
475
|
-
for (const resolved of resolvedMembers) {
|
|
476
|
-
if (resolved.properties?.[key]) {
|
|
477
|
-
allOfMembers.push(convertSchema({ schema: { properties: { [key]: resolved.properties[key] }, required: [key] } as SchemaObject }, options))
|
|
478
|
-
break
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (schema.properties) {
|
|
486
|
-
const { allOf: _allOf, ...schemaWithoutAllOf } = schema as SchemaObject & { allOf?: unknown[] }
|
|
487
|
-
allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf as SchemaObject }, options))
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return createSchema({
|
|
491
|
-
type: 'intersection',
|
|
492
|
-
members: allOfMembers,
|
|
493
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
494
|
-
})
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
|
|
499
|
-
*
|
|
500
|
-
* Both keywords are treated identically — their members are concatenated into a single union.
|
|
501
|
-
* When sibling `properties` are present alongside `oneOf`/`anyOf`, each union member is
|
|
502
|
-
* individually intersected with the shared properties node to match the OAS pattern of
|
|
503
|
-
* adding common fields next to a discriminated union.
|
|
504
|
-
*/
|
|
505
|
-
function convertUnion({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
506
|
-
const unionMembers = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])]
|
|
507
|
-
const unionBase = {
|
|
508
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
509
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : undefined,
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (schema.properties) {
|
|
513
|
-
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema as SchemaObject & { oneOf?: unknown[]; anyOf?: unknown[] }
|
|
514
|
-
const propertiesNode = convertSchema({ schema: schemaWithoutUnion as SchemaObject }, options)
|
|
515
|
-
|
|
516
|
-
return createSchema({
|
|
517
|
-
type: 'union',
|
|
518
|
-
...unionBase,
|
|
519
|
-
members: unionMembers.map((s) =>
|
|
520
|
-
createSchema({
|
|
521
|
-
type: 'intersection',
|
|
522
|
-
members: [convertSchema({ schema: s as SchemaObject }, options), propertiesNode],
|
|
523
|
-
}),
|
|
524
|
-
),
|
|
525
|
-
})
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return createSchema({
|
|
529
|
-
type: 'union',
|
|
530
|
-
...unionBase,
|
|
531
|
-
members: unionMembers.map((s) => convertSchema({ schema: s as SchemaObject }, options)),
|
|
532
|
-
})
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Converts an OAS 3.1 `const` schema into either a null scalar or a single-value `EnumSchemaNode`.
|
|
537
|
-
* `const: null` maps to a null scalar; any other value becomes a one-item enum so that generators
|
|
538
|
-
* can produce a precise literal type.
|
|
539
|
-
*/
|
|
540
|
-
function convertConst({ schema, name, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
541
|
-
const constValue = schema.const
|
|
542
|
-
|
|
543
|
-
if (constValue === null) {
|
|
544
|
-
return createSchema({
|
|
545
|
-
type: 'null',
|
|
546
|
-
primitive: 'null',
|
|
547
|
-
name,
|
|
548
|
-
title: schema.title,
|
|
549
|
-
description: schema.description,
|
|
550
|
-
deprecated: schema.deprecated,
|
|
551
|
-
nullable,
|
|
552
|
-
})
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const constPrimitive = getPrimitiveType(typeof constValue === 'number' ? 'number' : typeof constValue === 'boolean' ? 'boolean' : 'string')
|
|
556
|
-
return createSchema({
|
|
557
|
-
type: 'enum',
|
|
558
|
-
primitive: constPrimitive,
|
|
559
|
-
enumValues: [constValue as string | number | boolean],
|
|
560
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
561
|
-
})
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Handles `format`-based special types (date/time, uuid, email, blob, etc.).
|
|
566
|
-
* Returns `undefined` when the format should fall through to string handling
|
|
567
|
-
* (i.e. `format: 'date-time'` with `dateType: false`).
|
|
568
|
-
*/
|
|
569
|
-
function convertFormat({ schema, name, nullable, defaultValue, mergedOptions }: SchemaContext): SchemaNode | undefined {
|
|
570
|
-
const base = buildSchemaBase(schema, name, nullable, defaultValue)
|
|
571
|
-
|
|
572
|
-
// int64 is option-dependent so it can't live in the static FORMAT_MAP.
|
|
573
|
-
if (schema.format === 'int64') {
|
|
574
|
-
return createSchema({
|
|
575
|
-
type: mergedOptions.integerType === 'bigint' ? 'bigint' : 'integer',
|
|
576
|
-
primitive: 'integer',
|
|
577
|
-
...base,
|
|
578
|
-
min: schema.minimum,
|
|
579
|
-
max: schema.maximum,
|
|
580
|
-
exclusiveMinimum: typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined,
|
|
581
|
-
exclusiveMaximum: typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined,
|
|
582
|
-
})
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// date-time / date / time are option-dependent and can't live in the static FORMAT_MAP.
|
|
586
|
-
if (schema.format === 'date-time' || schema.format === 'date' || schema.format === 'time') {
|
|
587
|
-
const dateType = getDateType(mergedOptions, schema.format)
|
|
588
|
-
if (!dateType) return undefined // dateType: false → fall through to string
|
|
589
|
-
|
|
590
|
-
if (dateType.type === 'datetime') {
|
|
591
|
-
return createSchema({ ...base, primitive: 'string' as const, type: 'datetime', offset: dateType.offset, local: dateType.local })
|
|
592
|
-
}
|
|
593
|
-
return createSchema({ ...base, primitive: 'string' as const, type: dateType.type, representation: dateType.representation })
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
const specialType = formatToSchemaType(schema.format!)
|
|
597
|
-
if (!specialType) return undefined
|
|
598
|
-
|
|
599
|
-
const specialPrimitive: PrimitiveSchemaType = specialType === 'number' || specialType === 'integer' || specialType === 'bigint' ? specialType : 'string'
|
|
600
|
-
|
|
601
|
-
if (specialType === 'number' || specialType === 'integer' || specialType === 'bigint') {
|
|
602
|
-
return createSchema({ ...base, primitive: specialPrimitive, type: specialType })
|
|
603
|
-
}
|
|
604
|
-
return createSchema({ ...base, primitive: specialPrimitive, type: specialType as ScalarSchemaType })
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Converts an `enum` schema into an `EnumSchemaNode`.
|
|
609
|
-
*
|
|
610
|
-
* Handles several edge cases:
|
|
611
|
-
* - `{ type: 'array', enum }` (technically invalid OAS) — the enum is normalized into `items`.
|
|
612
|
-
* - `null` in enum values (OAS 3.0 nullable enum convention) — stripped and reflected as `nullable`.
|
|
613
|
-
* - `x-enumNames` / `x-enum-varnames` vendor extensions — produce named enum variants with explicit labels.
|
|
614
|
-
* - Numeric and boolean enums require a const-map representation because most generators cannot
|
|
615
|
-
* use string-enum syntax for non-string values.
|
|
616
|
-
*/
|
|
617
|
-
function convertEnum({ schema, name, nullable, type, options }: SchemaContext): SchemaNode {
|
|
618
|
-
// Malformed schema: `{ type: 'array', enum: [...] }` — normalize by moving the enum into items.
|
|
619
|
-
if (type === 'array') {
|
|
620
|
-
const rawSchema = schema as unknown as { items?: SchemaObject; enum?: unknown[] }
|
|
621
|
-
const isItemsObject = typeof rawSchema.items === 'object' && !Array.isArray(rawSchema.items)
|
|
622
|
-
const normalizedItems = { ...(isItemsObject ? rawSchema.items : {}), enum: schema.enum } as SchemaObject
|
|
623
|
-
const { enum: _enum, ...schemaWithoutEnum } = schema as SchemaObject & { enum?: unknown[] }
|
|
624
|
-
return convertSchema({ schema: { ...schemaWithoutEnum, items: normalizedItems } as SchemaObject, name }, options)
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// `null` in enum values is the OAS 3.0 convention for a nullable enum.
|
|
628
|
-
const nullInEnum = schema.enum!.includes(null)
|
|
629
|
-
const filteredValues = (nullInEnum ? schema.enum!.filter((v) => v !== null) : schema.enum!) as Array<string | number | boolean>
|
|
630
|
-
const enumNullable = nullable || nullInEnum || undefined
|
|
631
|
-
const enumDefault = schema.default === null && enumNullable ? undefined : schema.default
|
|
632
|
-
const enumPrimitive = getPrimitiveType(type)
|
|
633
|
-
|
|
634
|
-
const enumBase = {
|
|
635
|
-
type: 'enum' as const,
|
|
636
|
-
primitive: enumPrimitive,
|
|
637
|
-
name,
|
|
638
|
-
title: schema.title,
|
|
639
|
-
description: schema.description,
|
|
640
|
-
deprecated: schema.deprecated,
|
|
641
|
-
nullable: enumNullable,
|
|
642
|
-
readOnly: schema.readOnly,
|
|
643
|
-
writeOnly: schema.writeOnly,
|
|
644
|
-
default: enumDefault,
|
|
645
|
-
example: schema.example,
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// x-enumNames / x-enum-varnames: named variants with explicit labels take priority.
|
|
649
|
-
const extensionKey = ENUM_EXTENSION_KEYS.find((key) => key in schema)
|
|
650
|
-
if (extensionKey) {
|
|
651
|
-
const rawNames = (schema as Record<string, unknown>)[extensionKey] as Array<string | number>
|
|
652
|
-
const uniqueNames = [...new Set(rawNames)]
|
|
653
|
-
const enumType =
|
|
654
|
-
getPrimitiveType(type) === 'number' || getPrimitiveType(type) === 'integer'
|
|
655
|
-
? ('number' as const)
|
|
656
|
-
: getPrimitiveType(type) === 'boolean'
|
|
657
|
-
? ('boolean' as const)
|
|
658
|
-
: ('string' as const)
|
|
659
|
-
|
|
660
|
-
return createSchema({
|
|
661
|
-
...enumBase,
|
|
662
|
-
enumType,
|
|
663
|
-
namedEnumValues: uniqueNames.map((label, index) => ({
|
|
664
|
-
name: String(label),
|
|
665
|
-
value: filteredValues[index] ?? label,
|
|
666
|
-
format: enumType,
|
|
667
|
-
})),
|
|
668
|
-
})
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Number / integer enum — must use a const map since most generators can't use string-enum for numbers.
|
|
672
|
-
if (type === 'number' || type === 'integer') {
|
|
673
|
-
return createSchema({
|
|
674
|
-
...enumBase,
|
|
675
|
-
enumType: 'number' as const,
|
|
676
|
-
namedEnumValues: [...new Set(filteredValues)].map((value) => ({
|
|
677
|
-
name: String(value),
|
|
678
|
-
value: value as number,
|
|
679
|
-
format: 'number' as const,
|
|
680
|
-
})),
|
|
681
|
-
})
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Boolean enum — same const-map approach as numeric.
|
|
685
|
-
if (type === 'boolean') {
|
|
686
|
-
return createSchema({
|
|
687
|
-
...enumBase,
|
|
688
|
-
enumType: 'boolean' as const,
|
|
689
|
-
namedEnumValues: [...new Set(filteredValues)].map((value) => ({
|
|
690
|
-
name: String(value),
|
|
691
|
-
value: value as boolean,
|
|
692
|
-
format: 'boolean' as const,
|
|
693
|
-
})),
|
|
694
|
-
})
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Plain string enum (default path).
|
|
698
|
-
return createSchema({
|
|
699
|
-
...enumBase,
|
|
700
|
-
enumValues: [...new Set(filteredValues)],
|
|
701
|
-
})
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Converts an object-like schema (`type: 'object'`, `properties`, `additionalProperties`,
|
|
706
|
-
* or `patternProperties`) into an `ObjectSchemaNode`.
|
|
707
|
-
*
|
|
708
|
-
* When a `discriminator` is present, the discriminator property's schema is replaced with an
|
|
709
|
-
* enum of the mapping keys so generators can produce a precise literal-union type for it.
|
|
710
|
-
*
|
|
711
|
-
* Property optionality follows OAS semantics:
|
|
712
|
-
* - required + not nullable → `required: true`
|
|
713
|
-
* - not required + not nullable → `optional: true`
|
|
714
|
-
* - not required + nullable → `nullish: true`
|
|
715
|
-
*/
|
|
716
|
-
function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
|
|
717
|
-
// When a discriminator is present, override the discriminator property's schema to use
|
|
718
|
-
// an enum of the mapping keys for a precise literal-union type.
|
|
719
|
-
const resolvedSchema: SchemaObject = (() => {
|
|
720
|
-
if (!isDiscriminator(schema)) return schema
|
|
721
|
-
const propName = schema.discriminator.propertyName
|
|
722
|
-
if (!schema.properties?.[propName]) return schema
|
|
723
|
-
return {
|
|
724
|
-
...schema,
|
|
725
|
-
properties: {
|
|
726
|
-
...schema.properties,
|
|
727
|
-
[propName]: {
|
|
728
|
-
...(schema.properties[propName] as SchemaObject),
|
|
729
|
-
enum: schema.discriminator.mapping ? Object.keys(schema.discriminator.mapping) : undefined,
|
|
730
|
-
},
|
|
731
|
-
},
|
|
732
|
-
} as SchemaObject
|
|
733
|
-
})()
|
|
734
|
-
|
|
735
|
-
const properties: Array<PropertyNode> = resolvedSchema.properties
|
|
736
|
-
? Object.entries(resolvedSchema.properties).map(([propName, propSchema]) => {
|
|
737
|
-
const required = Array.isArray(resolvedSchema.required) ? resolvedSchema.required.includes(propName) : !!resolvedSchema.required
|
|
738
|
-
const resolvedPropSchema = propSchema as SchemaObject
|
|
739
|
-
const propNullable = isNullable(resolvedPropSchema)
|
|
740
|
-
const derivedPropName = name ? pascalCase([name, propName, mergedOptions.enumSuffix].filter(Boolean).join(' ')) : undefined
|
|
741
|
-
|
|
742
|
-
return createProperty({
|
|
743
|
-
name: propName,
|
|
744
|
-
schema: {
|
|
745
|
-
...convertSchema({ schema: resolvedPropSchema, name: derivedPropName }, options),
|
|
746
|
-
nullable: propNullable || undefined,
|
|
747
|
-
optional: !required && !propNullable ? true : undefined,
|
|
748
|
-
nullish: !required && propNullable ? true : undefined,
|
|
749
|
-
},
|
|
750
|
-
required,
|
|
751
|
-
})
|
|
752
|
-
})
|
|
753
|
-
: []
|
|
754
|
-
|
|
755
|
-
const additionalProperties = resolvedSchema.additionalProperties
|
|
756
|
-
let additionalPropertiesNode: SchemaNode | true | undefined
|
|
757
|
-
if (additionalProperties === true) {
|
|
758
|
-
additionalPropertiesNode = true
|
|
759
|
-
} else if (additionalProperties && Object.keys(additionalProperties).length > 0) {
|
|
760
|
-
additionalPropertiesNode = convertSchema({ schema: additionalProperties as SchemaObject }, options)
|
|
761
|
-
} else if (additionalProperties === false) {
|
|
762
|
-
additionalPropertiesNode = undefined
|
|
763
|
-
} else if (additionalProperties) {
|
|
764
|
-
additionalPropertiesNode = createSchema({ type: getUnknownType(mergedOptions) })
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
const rawPatternProperties =
|
|
768
|
-
'patternProperties' in resolvedSchema ? (resolvedSchema as unknown as { patternProperties?: Record<string, SchemaObject> }).patternProperties : undefined
|
|
769
|
-
|
|
770
|
-
const patternProperties = rawPatternProperties
|
|
771
|
-
? Object.fromEntries(
|
|
772
|
-
Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [
|
|
773
|
-
pattern,
|
|
774
|
-
(patternSchema as unknown) === true || Object.keys(patternSchema as object).length === 0
|
|
775
|
-
? createSchema({ type: getUnknownType(mergedOptions) })
|
|
776
|
-
: convertSchema({ schema: patternSchema as SchemaObject }, options),
|
|
777
|
-
]),
|
|
778
|
-
)
|
|
779
|
-
: undefined
|
|
780
|
-
|
|
781
|
-
return createSchema({
|
|
782
|
-
type: 'object',
|
|
783
|
-
primitive: 'object',
|
|
784
|
-
properties,
|
|
785
|
-
additionalProperties: additionalPropertiesNode,
|
|
786
|
-
patternProperties,
|
|
787
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
788
|
-
})
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
793
|
-
*
|
|
794
|
-
* Each `prefixItems` element maps to a positional tuple slot. An optional `items` schema
|
|
795
|
-
* after the prefix items is mapped to the rest parameter of the tuple.
|
|
796
|
-
*/
|
|
797
|
-
function convertTuple({ schema, name, nullable, defaultValue, options }: SchemaContext): SchemaNode {
|
|
798
|
-
const rawSchema = schema as unknown as { prefixItems: SchemaObject[]; items?: SchemaObject }
|
|
799
|
-
const tupleItems = rawSchema.prefixItems.map((item) => convertSchema({ schema: item }, options))
|
|
800
|
-
const rest = rawSchema.items ? convertSchema({ schema: rawSchema.items }, options) : undefined
|
|
801
|
-
|
|
802
|
-
return createSchema({
|
|
803
|
-
type: 'tuple',
|
|
804
|
-
primitive: 'array',
|
|
805
|
-
items: tupleItems,
|
|
806
|
-
rest,
|
|
807
|
-
min: schema.minItems,
|
|
808
|
-
max: schema.maxItems,
|
|
809
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
810
|
-
})
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* Converts a `type: 'array'` schema into an `ArraySchemaNode`.
|
|
815
|
-
*
|
|
816
|
-
* When the items schema is an inline enum, a name derived from the parent array's name and
|
|
817
|
-
* `enumSuffix` is forwarded so generators can emit a named enum declaration.
|
|
818
|
-
*/
|
|
819
|
-
function convertArray({ schema, name, nullable, defaultValue, options, mergedOptions }: SchemaContext): SchemaNode {
|
|
820
|
-
const rawSchema = schema as unknown as { items?: SchemaObject }
|
|
821
|
-
// When the array items schema contains an inline enum, derive a name from the parent
|
|
822
|
-
// array's name + enumSuffix so generators can emit a named enum declaration.
|
|
823
|
-
const rawItems = rawSchema.items as SchemaObject | undefined
|
|
824
|
-
const itemName = rawItems?.enum?.length && name ? pascalCase([name, mergedOptions.enumSuffix].join(' ')) : undefined
|
|
825
|
-
const items = rawSchema.items ? [convertSchema({ schema: rawSchema.items, name: itemName }, options)] : []
|
|
826
|
-
|
|
827
|
-
return createSchema({
|
|
828
|
-
type: 'array',
|
|
829
|
-
primitive: 'array',
|
|
830
|
-
items,
|
|
831
|
-
min: schema.minItems,
|
|
832
|
-
max: schema.maxItems,
|
|
833
|
-
unique: schema.uniqueItems ?? undefined,
|
|
834
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
835
|
-
})
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Converts a `type: 'string'` schema (without a special format) into a `StringSchemaNode`.
|
|
840
|
-
*/
|
|
841
|
-
function convertString({ schema, name, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
842
|
-
return createSchema({
|
|
843
|
-
type: 'string',
|
|
844
|
-
primitive: 'string',
|
|
845
|
-
min: schema.minLength,
|
|
846
|
-
max: schema.maxLength,
|
|
847
|
-
pattern: schema.pattern,
|
|
848
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
849
|
-
})
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* Converts a `type: 'number'` schema into a `NumberSchemaNode`.
|
|
854
|
-
*/
|
|
855
|
-
function convertNumber({ schema, name, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
856
|
-
return createSchema({
|
|
857
|
-
type: 'number',
|
|
858
|
-
primitive: 'number',
|
|
859
|
-
min: schema.minimum,
|
|
860
|
-
max: schema.maximum,
|
|
861
|
-
exclusiveMinimum: typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined,
|
|
862
|
-
exclusiveMaximum: typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined,
|
|
863
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
864
|
-
})
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
/**
|
|
868
|
-
* Converts a `type: 'integer'` schema into an `IntegerSchemaNode`.
|
|
869
|
-
*/
|
|
870
|
-
function convertInteger({ schema, name, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
871
|
-
return createSchema({
|
|
872
|
-
type: 'integer',
|
|
873
|
-
primitive: 'integer',
|
|
874
|
-
min: schema.minimum,
|
|
875
|
-
max: schema.maximum,
|
|
876
|
-
exclusiveMinimum: typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum : undefined,
|
|
877
|
-
exclusiveMaximum: typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum : undefined,
|
|
878
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
879
|
-
})
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* Converts a `type: 'boolean'` schema into a `BooleanSchemaNode`.
|
|
884
|
-
*/
|
|
885
|
-
function convertBoolean({ schema, name, nullable, defaultValue }: SchemaContext): SchemaNode {
|
|
886
|
-
return createSchema({
|
|
887
|
-
type: 'boolean',
|
|
888
|
-
primitive: 'boolean',
|
|
889
|
-
...buildSchemaBase(schema, name, nullable, defaultValue),
|
|
890
|
-
})
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
/**
|
|
894
|
-
* Converts an explicit `type: 'null'` or `const: null` schema into a `NullSchemaNode`.
|
|
895
|
-
*/
|
|
896
|
-
function convertNull({ schema, name, nullable }: SchemaContext): SchemaNode {
|
|
897
|
-
return createSchema({
|
|
898
|
-
type: 'null',
|
|
899
|
-
primitive: 'null',
|
|
900
|
-
name,
|
|
901
|
-
title: schema.title,
|
|
902
|
-
description: schema.description,
|
|
903
|
-
deprecated: schema.deprecated,
|
|
904
|
-
nullable,
|
|
905
|
-
})
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* Central dispatcher: converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
910
|
-
*
|
|
911
|
-
* Dispatch order (first match wins):
|
|
912
|
-
* 1. `$ref` pointer
|
|
913
|
-
* 2. `allOf` composition
|
|
914
|
-
* 3. `oneOf` / `anyOf` union
|
|
915
|
-
* 4. `const` literal (OAS 3.1)
|
|
916
|
-
* 5. `format`-based special type (date/time, uuid, blob, …)
|
|
917
|
-
* 6. OAS 3.1 `contentMediaType: 'application/octet-stream'` blob
|
|
918
|
-
* 7. OAS 3.1 multi-type array → union or fallthrough
|
|
919
|
-
* 8. Constraint-inferred type (minLength/maxLength → string; minimum/maximum → number)
|
|
920
|
-
* 9. `enum` values
|
|
921
|
-
* 10. Object / array / tuple / scalar by `type`
|
|
922
|
-
* 11. Empty schema fallback (`emptySchemaType` option)
|
|
923
|
-
*/
|
|
924
|
-
function convertSchema({ schema, name }: { schema: SchemaObject; name?: string }, options?: Partial<Options>): SchemaNode {
|
|
925
|
-
const mergedOptions: Options = { ...DEFAULT_OPTIONS, ...options }
|
|
926
|
-
// Flatten keyword-only allOf fragments (no $ref, no structural keys) into the parent
|
|
927
|
-
// schema before parsing, so simple annotation patterns don't produce needless intersections.
|
|
928
|
-
const flattenedSchema = flattenSchema(schema as unknown as Parameters<typeof flattenSchema>[0]) as SchemaObject | null
|
|
929
|
-
if (flattenedSchema && flattenedSchema !== (schema as unknown)) {
|
|
930
|
-
return convertSchema({ schema: flattenedSchema, name }, options)
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
const nullable = isNullable(schema) || undefined
|
|
934
|
-
const defaultValue = schema.default === null && nullable ? undefined : schema.default
|
|
935
|
-
// Normalize OAS 3.1 multi-type array to a single type string for the dispatch below.
|
|
936
|
-
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
|
|
937
|
-
|
|
938
|
-
const ctx: SchemaContext = { schema, name, nullable, defaultValue, type, options, mergedOptions }
|
|
939
|
-
|
|
940
|
-
// $ref — pointer to another definition.
|
|
941
|
-
// In OAS 3.0 siblings of $ref are technically ignored, but Kubb intentionally preserves them
|
|
942
|
-
// so that annotations like `pattern`, `description`, and `nullable` are reflected in generated code.
|
|
943
|
-
if (isReference(schema)) return convertRef(ctx)
|
|
944
|
-
|
|
945
|
-
// Composition keywords
|
|
946
|
-
if (schema.allOf?.length) return convertAllOf(ctx)
|
|
947
|
-
const unionMembers = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])]
|
|
948
|
-
if (unionMembers.length) return convertUnion(ctx)
|
|
949
|
-
|
|
950
|
-
// OAS 3.1 const — a single fixed value, semantically equivalent to a one-item enum.
|
|
951
|
-
// `const: undefined` falls through to the empty-type fallback.
|
|
952
|
-
if ('const' in schema && schema.const !== undefined) return convertConst(ctx)
|
|
953
|
-
|
|
954
|
-
// Format-based special types take precedence over `type`.
|
|
955
|
-
// `convertFormat` returns undefined when format should fall through to string (dateType: false).
|
|
956
|
-
// see https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7
|
|
957
|
-
if (schema.format) {
|
|
958
|
-
const formatResult = convertFormat(ctx)
|
|
959
|
-
if (formatResult) return formatResult
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// OAS 3.1: `contentMediaType: 'application/octet-stream'` on a string schema signals binary data.
|
|
963
|
-
if (schema.type === 'string' && (schema as SchemaObject & { contentMediaType?: string }).contentMediaType === 'application/octet-stream') {
|
|
964
|
-
return createSchema({ type: 'blob', primitive: 'string', ...buildSchemaBase(schema, name, nullable, defaultValue) })
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// OAS 3.1: `type` may be an array — e.g. `["string", "integer", "null"]`.
|
|
968
|
-
// `null` in the array is the 3.1 equivalent of `nullable: true`; strip it and set the flag.
|
|
969
|
-
// When 2+ non-null types remain, produce a union; when exactly 1 non-null type remains, fall through.
|
|
970
|
-
if (Array.isArray(schema.type) && schema.type.length > 1) {
|
|
971
|
-
const nonNullTypes = schema.type.filter((t) => t !== 'null') as string[]
|
|
972
|
-
const arrayNullable = schema.type.includes('null') || nullable || undefined
|
|
973
|
-
|
|
974
|
-
if (nonNullTypes.length > 1) {
|
|
975
|
-
return createSchema({
|
|
976
|
-
type: 'union',
|
|
977
|
-
members: nonNullTypes.map((t) => convertSchema({ schema: { ...schema, type: t } as SchemaObject }, options)),
|
|
978
|
-
...buildSchemaBase(schema, name, arrayNullable, defaultValue),
|
|
979
|
-
})
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
// Infer type from constraints when no explicit type is provided.
|
|
984
|
-
// minLength / maxLength / pattern → string; minimum / maximum → number.
|
|
985
|
-
// Note: minItems/maxItems do NOT infer array — arrays require an `items` key.
|
|
986
|
-
if (!type) {
|
|
987
|
-
if (schema.minLength !== undefined || schema.maxLength !== undefined || schema.pattern !== undefined) {
|
|
988
|
-
return convertString(ctx)
|
|
989
|
-
}
|
|
990
|
-
if (schema.minimum !== undefined || schema.maximum !== undefined) {
|
|
991
|
-
return convertNumber(ctx)
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
if (schema.enum?.length) return convertEnum(ctx)
|
|
996
|
-
if (type === 'object' || schema.properties || schema.additionalProperties || 'patternProperties' in schema) return convertObject(ctx)
|
|
997
|
-
if ('prefixItems' in schema) return convertTuple(ctx)
|
|
998
|
-
if (type === 'array' || 'items' in schema) return convertArray(ctx)
|
|
999
|
-
if (type === 'string') return convertString(ctx)
|
|
1000
|
-
if (type === 'number') return convertNumber(ctx)
|
|
1001
|
-
if (type === 'integer') return convertInteger(ctx)
|
|
1002
|
-
if (type === 'boolean') return convertBoolean(ctx)
|
|
1003
|
-
if (type === 'null') return convertNull(ctx)
|
|
1004
|
-
|
|
1005
|
-
const emptyType = getEmptySchemaType(mergedOptions)
|
|
1006
|
-
return createSchema({ type: emptyType as ScalarSchemaType, name, title: schema.title, description: schema.description })
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
/**
|
|
1010
|
-
* Converts a single dereferenced OAS parameter object into a `ParameterNode`.
|
|
1011
|
-
* When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
|
|
1012
|
-
*/
|
|
1013
|
-
function parseParameter(options: Options, param: Record<string, unknown>): ParameterNode {
|
|
1014
|
-
const schema =
|
|
1015
|
-
param['schema'] && !isReference(param['schema'] as object)
|
|
1016
|
-
? convertSchema({ schema: param['schema'] as SchemaObject }, options)
|
|
1017
|
-
: createSchema({ type: getUnknownType(options) })
|
|
1018
|
-
|
|
1019
|
-
return createParameter({
|
|
1020
|
-
name: param['name'] as string,
|
|
1021
|
-
in: param['in'] as ParameterLocation,
|
|
1022
|
-
schema,
|
|
1023
|
-
required: (param['required'] as boolean | undefined) ?? false,
|
|
1024
|
-
})
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Converts an OAS `Operation` into an `OperationNode`, resolving parameters,
|
|
1029
|
-
* request body, and all response codes into their AST node equivalents.
|
|
1030
|
-
*/
|
|
1031
|
-
function parseOperation(options: Options, oas: Oas, operation: Operation): OperationNode {
|
|
1032
|
-
const parameters: Array<ParameterNode> = operation.getParameters().map((param) => {
|
|
1033
|
-
const dereferenced = oas.dereferenceWithRef(param) as unknown as Record<string, unknown>
|
|
1034
|
-
|
|
1035
|
-
return parseParameter(options, dereferenced)
|
|
1036
|
-
})
|
|
1037
|
-
|
|
1038
|
-
const requestBodySchema = oas.getRequestSchema(operation)
|
|
1039
|
-
const requestBody = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : undefined
|
|
1040
|
-
|
|
1041
|
-
const responses: Array<ResponseNode> = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1042
|
-
const responseObj = operation.getResponseByStatusCode(statusCode)
|
|
1043
|
-
const responseSchema = oas.getResponseSchema(operation, statusCode)
|
|
1044
|
-
|
|
1045
|
-
const schema = responseSchema && Object.keys(responseSchema).length > 0 ? convertSchema({ schema: responseSchema }, options) : undefined
|
|
1046
|
-
|
|
1047
|
-
const description = typeof responseObj === 'object' && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : undefined
|
|
1048
|
-
|
|
1049
|
-
const rawContent =
|
|
1050
|
-
typeof responseObj === 'object' && responseObj !== null && !Array.isArray(responseObj)
|
|
1051
|
-
? (responseObj as { content?: Record<string, unknown> }).content
|
|
1052
|
-
: undefined
|
|
1053
|
-
|
|
1054
|
-
const mediaType = rawContent ? toMediaType(Object.keys(rawContent)[0] ?? '') : toMediaType(operation.contentType ?? '')
|
|
1055
|
-
|
|
1056
|
-
return createResponse({
|
|
1057
|
-
statusCode: statusCode as StatusCode,
|
|
1058
|
-
description,
|
|
1059
|
-
schema,
|
|
1060
|
-
mediaType,
|
|
1061
|
-
})
|
|
1062
|
-
})
|
|
1063
|
-
|
|
1064
|
-
return createOperation({
|
|
1065
|
-
operationId: operation.getOperationId(),
|
|
1066
|
-
method: operation.method.toUpperCase() as HttpMethod,
|
|
1067
|
-
path: operation.path,
|
|
1068
|
-
tags: operation.getTags().map((tag) => tag.name),
|
|
1069
|
-
summary: operation.getSummary() || undefined,
|
|
1070
|
-
description: operation.getDescription() || undefined,
|
|
1071
|
-
deprecated: operation.isDeprecated() || undefined,
|
|
1072
|
-
parameters,
|
|
1073
|
-
requestBody,
|
|
1074
|
-
responses,
|
|
1075
|
-
})
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
/**
|
|
1079
|
-
* Converts an OpenAPI/Swagger spec (wrapped in a Kubb `Oas` instance) into
|
|
1080
|
-
* a `RootNode` — the top-level node of the `@kubb/ast` tree.
|
|
1081
|
-
*/
|
|
1082
|
-
function buildAst<TOptions extends Partial<Options> = object>(options?: TOptions): RootNode {
|
|
1083
|
-
const mergedOptions: Options = { ...DEFAULT_OPTIONS, ...options }
|
|
1084
|
-
|
|
1085
|
-
const schemas: Array<SchemaNode> = Object.entries(schemaObjects).map(([name, schemaObject]) =>
|
|
1086
|
-
convertSchema({ schema: schemaObject as SchemaObject, name }, mergedOptions),
|
|
1087
|
-
)
|
|
1088
|
-
|
|
1089
|
-
const paths = oas.getPaths()
|
|
1090
|
-
|
|
1091
|
-
const operations: Array<OperationNode> = Object.entries(paths).flatMap(([_path, methods]) =>
|
|
1092
|
-
Object.entries(methods)
|
|
1093
|
-
.map(([, operation]) => (operation ? parseOperation(mergedOptions, oas, operation) : null))
|
|
1094
|
-
.filter((op): op is OperationNode => op !== null),
|
|
1095
|
-
)
|
|
1096
|
-
|
|
1097
|
-
return createRoot({ schemas, operations })
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
/**
|
|
1101
|
-
* Walks a `SchemaNode` tree and resolves all `ref` node names through the provided callbacks.
|
|
1102
|
-
*
|
|
1103
|
-
* `resolveName` handles all schema types; `resolveEnumName` (when provided) takes precedence
|
|
1104
|
-
* for `enum` nodes, enabling a separate naming strategy for enums (e.g. different suffix).
|
|
1105
|
-
*
|
|
1106
|
-
* Collision-resolved names (from `nameMapping`) are applied before user-supplied resolvers.
|
|
1107
|
-
*/
|
|
1108
|
-
function resolveRefs(node: SchemaNode, resolveName: (ref: string) => string | undefined, resolveEnumName?: (name: string) => string | undefined): SchemaNode {
|
|
1109
|
-
return transform(node, {
|
|
1110
|
-
schema(schemaNode) {
|
|
1111
|
-
const schemaRef = narrowSchema(schemaNode, schemaTypes.ref)
|
|
1112
|
-
|
|
1113
|
-
if (schemaRef && (schemaRef.ref || schemaRef.name)) {
|
|
1114
|
-
const rawRef = schemaRef.ref ?? schemaRef.name!
|
|
1115
|
-
const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef)
|
|
1116
|
-
if (resolved) {
|
|
1117
|
-
return { ...schemaNode, name: resolved }
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
if (schemaNode.type === 'enum' && schemaNode.name) {
|
|
1122
|
-
const resolved = (resolveEnumName ?? resolveName)(schemaNode.name)
|
|
1123
|
-
if (resolved) {
|
|
1124
|
-
return { ...schemaNode, name: resolved }
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
},
|
|
1128
|
-
}) as SchemaNode
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
* Collects all `KubbFile.Import` descriptors needed by a `SchemaNode` tree.
|
|
1133
|
-
*
|
|
1134
|
-
* Walks the tree looking for `ref` nodes, verifies each `$ref` is resolvable in the spec,
|
|
1135
|
-
* applies collision-resolved names from `nameMapping`, and calls `resolve` to obtain the
|
|
1136
|
-
* import path and name. Returns an empty array for refs that cannot be resolved.
|
|
1137
|
-
*/
|
|
1138
|
-
function getImports(node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string } | undefined): Array<KubbFile.Import> {
|
|
1139
|
-
return collect<KubbFile.Import>(node, {
|
|
1140
|
-
schema(schemaNode): KubbFile.Import | undefined {
|
|
1141
|
-
if (schemaNode.type !== 'ref' || !schemaNode.ref) return
|
|
1142
|
-
// Use the OAS instance to verify this $ref is importable (exists in the spec).
|
|
1143
|
-
if (!oas.get(schemaNode.ref)) return
|
|
1144
|
-
|
|
1145
|
-
const rawName = extractRefName(schemaNode.ref)
|
|
1146
|
-
|
|
1147
|
-
// Apply collision-resolved name if available.
|
|
1148
|
-
const schemaName = nameMapping.get(rawName) ?? rawName
|
|
1149
|
-
const result = resolve(schemaName)
|
|
1150
|
-
if (!result) return
|
|
1151
|
-
|
|
1152
|
-
return { name: [result.name], path: result.path }
|
|
1153
|
-
},
|
|
1154
|
-
})
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
return {
|
|
1158
|
-
buildAst,
|
|
1159
|
-
convertSchema,
|
|
1160
|
-
resolveRefs,
|
|
1161
|
-
getImports,
|
|
1162
|
-
} as OasParser
|
|
1163
|
-
}
|