@kubb/adapter-oas 5.0.0-beta.62 → 5.0.0-beta.63
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 +216 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +216 -94
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +1 -1
- package/src/constants.ts +13 -14
- package/src/dedupe.ts +237 -0
- package/src/dialect.ts +2 -20
- package/src/discriminator.ts +1 -1
- package/src/factory.ts +1 -1
- package/src/mime.ts +2 -3
- package/src/operation.ts +2 -2
- package/src/parser.ts +16 -22
- package/src/refs.ts +4 -3
- package/src/resolvers.ts +13 -5
- package/src/stream.ts +16 -59
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/adapter-oas",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.63",
|
|
4
4
|
"description": "OpenAPI and Swagger adapter for Kubb. Parses and validates OAS 2.0/3.x specifications into a @kubb/ast RootNode for downstream code generation plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"api-ref-bundler": "0.5.1",
|
|
47
47
|
"swagger2openapi": "^7.0.8",
|
|
48
48
|
"yaml": "^2.9.0",
|
|
49
|
-
"@kubb/ast": "5.0.0-beta.
|
|
50
|
-
"@kubb/core": "5.0.0-beta.
|
|
49
|
+
"@kubb/ast": "5.0.0-beta.63",
|
|
50
|
+
"@kubb/core": "5.0.0-beta.63"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/json-schema": "^7.0.15",
|
package/src/adapter.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { collect, narrowSchema } from '@kubb/ast'
|
|
|
10
10
|
import { extractRefName } from '@kubb/ast/utils'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
|
|
14
14
|
*/
|
|
15
15
|
export const adapterOasName = 'oas' satisfies AdapterOas['name']
|
|
16
16
|
|
package/src/constants.ts
CHANGED
|
@@ -5,10 +5,9 @@ import { ast } from '@kubb/core'
|
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```ts
|
|
8
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
8
|
+
* import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
|
|
9
9
|
*
|
|
10
|
-
* const
|
|
11
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
10
|
+
* const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
12
11
|
* ```
|
|
13
12
|
*/
|
|
14
13
|
export const DEFAULT_PARSER_OPTIONS = {
|
|
@@ -68,13 +67,21 @@ export const MERGE_DEFAULT_VERSION = '1.0.0' as const
|
|
|
68
67
|
*/
|
|
69
68
|
export const structuralKeys = new Set(['properties', 'items', 'additionalProperties', 'oneOf', 'anyOf', 'allOf', 'not'] as const)
|
|
70
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
72
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
73
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
74
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
75
|
+
*/
|
|
76
|
+
export const specialCasedFormats: ReadonlySet<string> = new Set(['int64', 'date-time', 'date', 'time'])
|
|
77
|
+
|
|
71
78
|
/**
|
|
72
79
|
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
73
80
|
*
|
|
74
81
|
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
75
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
76
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
77
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
82
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
83
|
+
* separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
84
|
+
* and `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
78
85
|
*
|
|
79
86
|
* @example
|
|
80
87
|
* ```ts
|
|
@@ -85,14 +92,6 @@ export const structuralKeys = new Set(['properties', 'items', 'additionalPropert
|
|
|
85
92
|
* formatMap['float'] // 'number'
|
|
86
93
|
* ```
|
|
87
94
|
*/
|
|
88
|
-
/**
|
|
89
|
-
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
90
|
-
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
91
|
-
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
92
|
-
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
93
|
-
*/
|
|
94
|
-
export const specialCasedFormats: ReadonlySet<string> = new Set(['int64', 'date-time', 'date', 'time'])
|
|
95
|
-
|
|
96
95
|
export const formatMap = {
|
|
97
96
|
uuid: 'uuid',
|
|
98
97
|
email: 'email',
|
package/src/dedupe.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { containsCircularRef, extractRefName } from '@kubb/ast/utils'
|
|
2
|
+
import { ast } from '@kubb/core'
|
|
3
|
+
import { SCHEMA_REF_PREFIX } from './constants.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The destination a deduplicated shape points at: the shared schema name and the
|
|
7
|
+
* synthetic `$ref` path stored on the generated `ref` nodes.
|
|
8
|
+
*/
|
|
9
|
+
type Target = {
|
|
10
|
+
name: string
|
|
11
|
+
ref: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The result of {@link plan}: the shared definitions to prepend to the schema list, plus the
|
|
16
|
+
* rewriting behavior that repoints duplicate shapes at their shared target. The lookup maps stay
|
|
17
|
+
* private to the closure, so callers interact with one object instead of three collections.
|
|
18
|
+
*/
|
|
19
|
+
export type Plan = {
|
|
20
|
+
/**
|
|
21
|
+
* New top-level schema definitions created for inline shapes that had no existing named
|
|
22
|
+
* component. Nested duplicates inside each definition are already collapsed.
|
|
23
|
+
*/
|
|
24
|
+
extracted: Array<ast.SchemaNode>
|
|
25
|
+
/**
|
|
26
|
+
* Rewrites an operation or nested schema, replacing every duplicate sub-schema with a `ref` to
|
|
27
|
+
* its shared target. Replacing a node prunes its subtree, so nested duplicates inside a replaced
|
|
28
|
+
* shape are not visited again. A `ref` to a duplicate top-level schema is repointed at the first
|
|
29
|
+
* schema with the same content.
|
|
30
|
+
*/
|
|
31
|
+
apply<T extends ast.Node>(node: T): T
|
|
32
|
+
/**
|
|
33
|
+
* Rewrites a top-level schema. A schema whose content duplicates a different shared one becomes a
|
|
34
|
+
* `ref` alias to it (keeping its own name and docs). Otherwise its nested duplicates collapse
|
|
35
|
+
* while its own root is preserved.
|
|
36
|
+
*/
|
|
37
|
+
applyTopLevel(node: ast.SchemaNode): ast.SchemaNode
|
|
38
|
+
/**
|
|
39
|
+
* Whether a top-level name duplicates an earlier schema with the same content. Such a schema is
|
|
40
|
+
* never emitted, since every reference to it is repointed at the first schema with that content.
|
|
41
|
+
*/
|
|
42
|
+
isAlias(name: string): boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Runtime state {@link plan} reads from the current parse: schema names that take part in a
|
|
47
|
+
* circular chain (never extracted, extracting them would break the cycle) and the names already in
|
|
48
|
+
* use (so extracted definitions resolve collisions).
|
|
49
|
+
*/
|
|
50
|
+
export type DedupeContext = {
|
|
51
|
+
circularSchemas: ReadonlySet<string>
|
|
52
|
+
usedNames: Set<string>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Minimum occurrences before a shape is deduplicated.
|
|
57
|
+
*/
|
|
58
|
+
const MIN_OCCURRENCES = 2
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
62
|
+
* usage-slot and documentation fields that are not part of the shared type.
|
|
63
|
+
*/
|
|
64
|
+
function createRefNode(node: ast.SchemaNode, target: Target): ast.SchemaNode {
|
|
65
|
+
return ast.factory.createSchema({
|
|
66
|
+
type: 'ref',
|
|
67
|
+
name: target.name,
|
|
68
|
+
ref: target.ref,
|
|
69
|
+
optional: node.optional,
|
|
70
|
+
nullish: node.nullish,
|
|
71
|
+
readOnly: node.readOnly,
|
|
72
|
+
writeOnly: node.writeOnly,
|
|
73
|
+
deprecated: node.deprecated,
|
|
74
|
+
description: node.description,
|
|
75
|
+
default: node.default,
|
|
76
|
+
example: node.example,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Strips usage-slot flags from an extracted definition and applies its name.
|
|
82
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
83
|
+
*/
|
|
84
|
+
function cleanDefinition(node: ast.SchemaNode, name: string): ast.SchemaNode {
|
|
85
|
+
return { ...node, name, optional: undefined, nullish: undefined }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns `true` when a node is eligible for deduplication. Only enums and objects qualify, and
|
|
90
|
+
* object shapes that take part in a circular chain are rejected so recursive structures are not
|
|
91
|
+
* extracted (which would break the cycle).
|
|
92
|
+
*/
|
|
93
|
+
function isCandidate(node: ast.SchemaNode, circularSchemas: ReadonlySet<string>): boolean {
|
|
94
|
+
if (node.type === 'enum') return true
|
|
95
|
+
if (node.type !== 'object') return false
|
|
96
|
+
if (node.name && circularSchemas.has(node.name)) return false
|
|
97
|
+
return !containsCircularRef(node, { circularSchemas })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Produces the name for an inline shape with no existing named component, resolving
|
|
102
|
+
* collisions against `usedNames`. Returns `null` for an unnamed shape, which stays inline.
|
|
103
|
+
*/
|
|
104
|
+
function nameFor(node: ast.SchemaNode, usedNames: Set<string>): string | null {
|
|
105
|
+
const base = node.name
|
|
106
|
+
if (!base) return null
|
|
107
|
+
|
|
108
|
+
let name = base
|
|
109
|
+
let counter = 2
|
|
110
|
+
while (usedNames.has(name)) {
|
|
111
|
+
name = `${base}${counter++}`
|
|
112
|
+
}
|
|
113
|
+
usedNames.add(name)
|
|
114
|
+
return name
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Scans a forest of schema and operation nodes and produces a {@link Plan}.
|
|
119
|
+
*
|
|
120
|
+
* A shape that occurs at least {@link MIN_OCCURRENCES} times is deduplicated: if any occurrence is
|
|
121
|
+
* a named top-level schema, the first one becomes the target (so other top-level duplicates and
|
|
122
|
+
* inline copies turn into references to it). Other top-level names with the same content are
|
|
123
|
+
* recorded as aliases. Otherwise a new definition is extracted using {@link nameFor}. The returned
|
|
124
|
+
* plan rewrites nodes against those decisions.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const dedupePlan = plan([...schemaNodes, ...operationNodes], { circularSchemas, usedNames })
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function plan(roots: ReadonlyArray<ast.Node>, context: DedupeContext): Plan {
|
|
132
|
+
const { circularSchemas, usedNames } = context
|
|
133
|
+
|
|
134
|
+
const topLevelNodes = new Set<ast.SchemaNode>()
|
|
135
|
+
|
|
136
|
+
type Group = {
|
|
137
|
+
count: number
|
|
138
|
+
representative: ast.SchemaNode
|
|
139
|
+
topLevelNames: Array<string>
|
|
140
|
+
}
|
|
141
|
+
const groups = new Map<string, Group>()
|
|
142
|
+
|
|
143
|
+
for (const root of roots) {
|
|
144
|
+
if (root.kind === 'Schema') topLevelNodes.add(root)
|
|
145
|
+
for (const schemaNode of ast.collect<ast.SchemaNode>(root, { schema: (node) => node })) {
|
|
146
|
+
if (!isCandidate(schemaNode, circularSchemas)) continue
|
|
147
|
+
|
|
148
|
+
const signature = ast.signatureOf(schemaNode)
|
|
149
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name
|
|
150
|
+
const group = groups.get(signature)
|
|
151
|
+
if (group) {
|
|
152
|
+
group.count++
|
|
153
|
+
if (isTopLevel) group.topLevelNames.push(schemaNode.name!)
|
|
154
|
+
} else {
|
|
155
|
+
groups.set(signature, { count: 1, representative: schemaNode, topLevelNames: isTopLevel ? [schemaNode.name!] : [] })
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const bySignature = new Map<string, Target>()
|
|
161
|
+
const byName = new Map<string, Target>()
|
|
162
|
+
const pendingExtractions: Array<{ name: string; representative: ast.SchemaNode }> = []
|
|
163
|
+
|
|
164
|
+
for (const [signature, group] of groups) {
|
|
165
|
+
if (group.count < MIN_OCCURRENCES) continue
|
|
166
|
+
|
|
167
|
+
const [firstName, ...duplicateNames] = group.topLevelNames
|
|
168
|
+
if (firstName) {
|
|
169
|
+
const target: Target = { name: firstName, ref: `${SCHEMA_REF_PREFIX}${firstName}` }
|
|
170
|
+
bySignature.set(signature, target)
|
|
171
|
+
for (const duplicate of duplicateNames) {
|
|
172
|
+
byName.set(duplicate, target)
|
|
173
|
+
}
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const name = nameFor(group.representative, usedNames)
|
|
178
|
+
if (!name) continue
|
|
179
|
+
|
|
180
|
+
bySignature.set(signature, { name, ref: `${SCHEMA_REF_PREFIX}${name}` })
|
|
181
|
+
pendingExtractions.push({ name, representative: group.representative })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Rewrites a node against the resolved targets. `skipRootMatch` keeps a definition's own root from
|
|
185
|
+
// turning into a reference to itself. Nested duplicates are still collapsed.
|
|
186
|
+
function rewrite<T extends ast.Node>(node: T, skipRootMatch: boolean): T {
|
|
187
|
+
if (bySignature.size === 0 && byName.size === 0) return node
|
|
188
|
+
|
|
189
|
+
const root = node
|
|
190
|
+
|
|
191
|
+
return ast.transform(node, {
|
|
192
|
+
schema(schemaNode) {
|
|
193
|
+
if (schemaNode.type === 'ref') {
|
|
194
|
+
const refName = schemaNode.ref ? extractRefName(schemaNode.ref) : schemaNode.name
|
|
195
|
+
const target = refName ? byName.get(refName) : undefined
|
|
196
|
+
|
|
197
|
+
return target ? { ...schemaNode, name: target.name, ref: target.ref } : undefined
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (skipRootMatch && schemaNode === root) return undefined
|
|
201
|
+
|
|
202
|
+
const target = bySignature.get(ast.signatureOf(schemaNode))
|
|
203
|
+
if (!target) return undefined
|
|
204
|
+
|
|
205
|
+
return createRefNode(schemaNode, target)
|
|
206
|
+
},
|
|
207
|
+
}) as T
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Build extracted definitions only after every target name is known, so nested
|
|
211
|
+
// duplicates inside a definition also resolve to refs.
|
|
212
|
+
const extracted = pendingExtractions.map(({ name, representative }) => cleanDefinition(rewrite(representative, true), name))
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
extracted,
|
|
216
|
+
apply<T extends ast.Node>(node: T): T {
|
|
217
|
+
return rewrite(node, false)
|
|
218
|
+
},
|
|
219
|
+
applyTopLevel(node: ast.SchemaNode): ast.SchemaNode {
|
|
220
|
+
const target = bySignature.get(ast.signatureOf(node))
|
|
221
|
+
if (target && target.name !== node.name) {
|
|
222
|
+
return ast.factory.createSchema({
|
|
223
|
+
type: 'ref',
|
|
224
|
+
name: node.name ?? null,
|
|
225
|
+
ref: target.ref,
|
|
226
|
+
description: node.description,
|
|
227
|
+
deprecated: node.deprecated,
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return rewrite(node, true)
|
|
232
|
+
},
|
|
233
|
+
isAlias(name: string): boolean {
|
|
234
|
+
return byName.has(name)
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
}
|
package/src/dialect.ts
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
import { ast } from '@kubb/core'
|
|
2
|
+
import { plan } from './dedupe.ts'
|
|
2
3
|
import { isDiscriminator, isNullable, isReference } from './guards.ts'
|
|
3
4
|
import { resolveRef } from './refs.ts'
|
|
4
5
|
import type { SchemaObject } from './types.ts'
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
-
* Derives a schema's `optional`/`nullish` flags from a parent's `required` value and the
|
|
8
|
-
* schema's own `nullable`. How "required" and "nullable" combine is OpenAPI-specific, so it
|
|
9
|
-
* lives in the dialect.
|
|
10
|
-
*
|
|
11
|
-
* - Non-required + non-nullable → `optional: true`.
|
|
12
|
-
* - Non-required + nullable → `nullish: true`.
|
|
13
|
-
* - Required → both flags cleared.
|
|
14
|
-
*/
|
|
15
|
-
function optionality(schema: ast.SchemaNode, required: boolean): ast.SchemaNode {
|
|
16
|
-
const nullable = schema.nullable ?? false
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
...schema,
|
|
20
|
-
optional: !required && !nullable ? true : undefined,
|
|
21
|
-
nullish: !required && nullable ? true : undefined,
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
7
|
/**
|
|
26
8
|
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
27
9
|
*
|
|
@@ -49,8 +31,8 @@ export const oasDialect = ast.defineDialect({
|
|
|
49
31
|
isDiscriminator,
|
|
50
32
|
isBinary: (schema: SchemaObject) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
51
33
|
resolveRef,
|
|
52
|
-
optionality,
|
|
53
34
|
},
|
|
35
|
+
dedupe: { plan },
|
|
54
36
|
})
|
|
55
37
|
|
|
56
38
|
/**
|
package/src/discriminator.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type DiscriminatorTarget = {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Maps each child schema name to its discriminator patch data by scanning the given
|
|
11
11
|
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
12
12
|
*
|
|
13
13
|
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
package/src/factory.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type ValidateDocumentOptions = {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Loads and
|
|
21
|
+
* Loads and bundles an OpenAPI document, returning the raw `Document`.
|
|
22
22
|
*
|
|
23
23
|
* Accepts a file path string or an already-parsed document object. File paths and URLs are
|
|
24
24
|
* bundled via `api-ref-bundler`, hoisting external file schemas into named `components.schemas`
|
package/src/mime.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MIME type fragments that mark a media type as JSON-like.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `application/vnd.api+json`.
|
|
4
|
+
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
5
|
+
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
7
6
|
*/
|
|
8
7
|
const jsonMimeFragments = ['application/json', 'application/x-json', 'text/json', 'text/x-json', '+json'] as const
|
|
9
8
|
|
package/src/operation.ts
CHANGED
|
@@ -125,8 +125,8 @@ export function getRequestContent({
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
129
|
-
*
|
|
128
|
+
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
129
|
+
* when several are declared), then the first declared one, defaulting to `'application/json'`.
|
|
130
130
|
*/
|
|
131
131
|
export function getRequestContentType({ document, operation }: OperationContext): string {
|
|
132
132
|
const content = getRequestBodyContent({ document, operation })
|
package/src/parser.ts
CHANGED
|
@@ -606,17 +606,14 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
606
606
|
const propNode = parseSchema({ schema: resolvedPropSchema, name: resolvedChildName }, rawOptions)
|
|
607
607
|
const schemaNode = nameEnums(propNode, { parentName: name, propName, enumSuffix: options.enumSuffix })
|
|
608
608
|
|
|
609
|
-
return ast.factory.createProperty(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
615
|
-
},
|
|
616
|
-
required,
|
|
609
|
+
return ast.factory.createProperty({
|
|
610
|
+
name: propName,
|
|
611
|
+
schema: {
|
|
612
|
+
...schemaNode,
|
|
613
|
+
nullable: schemaNode.type === 'null' ? undefined : propNullable || undefined,
|
|
617
614
|
},
|
|
618
|
-
|
|
619
|
-
)
|
|
615
|
+
required,
|
|
616
|
+
})
|
|
620
617
|
})
|
|
621
618
|
: []
|
|
622
619
|
|
|
@@ -894,18 +891,15 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
894
891
|
? parseSchema({ schema: param['schema'] as SchemaObject, name: schemaName }, options)
|
|
895
892
|
: ast.factory.createSchema({ type: typeOptionMap.get(options.unknownType)! })
|
|
896
893
|
|
|
897
|
-
return ast.factory.createParameter(
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
schema
|
|
902
|
-
|
|
903
|
-
description: (param['description'] as string | undefined) ?? schema.description,
|
|
904
|
-
},
|
|
905
|
-
required,
|
|
894
|
+
return ast.factory.createParameter({
|
|
895
|
+
name: paramName,
|
|
896
|
+
in: param['in'] as ast.ParameterLocation,
|
|
897
|
+
schema: {
|
|
898
|
+
...schema,
|
|
899
|
+
description: (param['description'] as string | undefined) ?? schema.description,
|
|
906
900
|
},
|
|
907
|
-
|
|
908
|
-
)
|
|
901
|
+
required,
|
|
902
|
+
})
|
|
909
903
|
}
|
|
910
904
|
|
|
911
905
|
/**
|
|
@@ -984,7 +978,7 @@ export function createSchemaParser(ctx: OasParserContext, dialect: OasDialect =
|
|
|
984
978
|
return [
|
|
985
979
|
ast.factory.createContent({
|
|
986
980
|
contentType: ct,
|
|
987
|
-
schema:
|
|
981
|
+
schema: ast.optionality(parseSchema({ schema, name: requestBodyName }, options), requestBodyMeta.required),
|
|
988
982
|
keysToOmit: collectPropertyKeysByFlag(schema, 'readOnly'),
|
|
989
983
|
}),
|
|
990
984
|
]
|
package/src/refs.ts
CHANGED
|
@@ -7,12 +7,13 @@ const _refCache = new WeakMap<Document, Map<string, unknown>>()
|
|
|
7
7
|
/**
|
|
8
8
|
* Resolves a local JSON pointer reference from a document.
|
|
9
9
|
*
|
|
10
|
-
* Accepts `#/...` refs. Returns `null` for empty or non-local
|
|
11
|
-
*
|
|
10
|
+
* Accepts `#/...` refs. Returns `null` for an empty or non-local ref. When the pointer cannot be
|
|
11
|
+
* resolved, reports a `refNotFound` diagnostic into the active build and returns `null`. Outside a
|
|
12
|
+
* build there is no sink to collect it, so it throws instead.
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```ts
|
|
15
|
-
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
16
|
+
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
16
17
|
* ```
|
|
17
18
|
*/
|
|
18
19
|
export function resolveRef<T = unknown>(document: Document, $ref: string): T | null {
|
package/src/resolvers.ts
CHANGED
|
@@ -59,9 +59,9 @@ export function getSchemaType(format: string): ast.SchemaType | null {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry
|
|
63
|
-
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to
|
|
64
|
-
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
62
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry, plus the
|
|
63
|
+
* `specialCasedFormats` that `convertFormat` handles directly. False means the format falls back to
|
|
64
|
+
* the base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
65
65
|
* diagnostic in step with the parser as `formatMap` grows.
|
|
66
66
|
*/
|
|
67
67
|
export function isHandledFormat(format: string): boolean {
|
|
@@ -86,7 +86,7 @@ export type OperationsOptions = {
|
|
|
86
86
|
/**
|
|
87
87
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
88
88
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
89
|
-
* `$ref`
|
|
89
|
+
* Each `$ref` parameter is dereferenced via `dereferenceWithRef` before merging.
|
|
90
90
|
*
|
|
91
91
|
* @example
|
|
92
92
|
* ```ts
|
|
@@ -220,7 +220,8 @@ export function getRequestSchema(document: Document, operation: Operation, optio
|
|
|
220
220
|
type SchemaSourceMode = 'schemas' | 'responses' | 'requestBodies'
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
|
-
* A schema annotated with its component section source and original name.
|
|
223
|
+
* A schema annotated with its component section source and original name. `getSchemas` uses this
|
|
224
|
+
* to resolve name collisions across sources.
|
|
224
225
|
*/
|
|
225
226
|
type SchemaWithMetadata = {
|
|
226
227
|
schema: SchemaObject
|
|
@@ -234,6 +235,10 @@ export type GetSchemasOptions = {
|
|
|
234
235
|
|
|
235
236
|
export type GetSchemasResult = {
|
|
236
237
|
schemas: Record<string, SchemaObject>
|
|
238
|
+
/**
|
|
239
|
+
* Maps each original component pointer (`#/components/<source>/<name>`) to the
|
|
240
|
+
* collision-resolved unique name used as the key in `schemas`.
|
|
241
|
+
*/
|
|
237
242
|
nameMapping: Map<string, string>
|
|
238
243
|
}
|
|
239
244
|
|
|
@@ -248,7 +253,10 @@ export type GetSchemasResult = {
|
|
|
248
253
|
* ```ts
|
|
249
254
|
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
250
255
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
256
|
+
* ```
|
|
251
257
|
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
252
260
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
253
261
|
* // returned unchanged, contains a $ref
|
|
254
262
|
* ```
|