@kubb/adapter-oas 5.0.0-alpha.2 → 5.0.0-alpha.4
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 +235 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +27 -0
- package/dist/index.js +235 -101
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +16 -0
- package/src/oas/types.ts +19 -0
- package/src/parser.ts +104 -152
- package/src/types.ts +6 -0
- package/src/utils.ts +161 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { collect, createProperty, createSchema, narrowSchema } from '@kubb/ast'
|
|
2
|
+
import type { SchemaNode } from '@kubb/ast/types'
|
|
3
|
+
import type { KubbFile } from '@kubb/fabric-core/types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the schema name from a `$ref` string.
|
|
7
|
+
* For `#/components/schemas/Order` this returns `'Order'`.
|
|
8
|
+
* Falls back to the full ref string when no slash is present.
|
|
9
|
+
*/
|
|
10
|
+
export function extractRefName($ref: string): string {
|
|
11
|
+
return $ref.split('/').at(-1) ?? $ref
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Replaces the discriminator property's schema inside an `ObjectSchemaNode`
|
|
16
|
+
* with an enum of the given `values`.
|
|
17
|
+
*
|
|
18
|
+
* - When `enumName` is provided the enum is named, which lets printers emit a
|
|
19
|
+
* standalone enum declaration + type reference (e.g. `PetTypeEnum`).
|
|
20
|
+
* - When `enumName` is omitted the enum stays anonymous, so printers inline it
|
|
21
|
+
* as a literal union (e.g. `'dog'`).
|
|
22
|
+
*
|
|
23
|
+
* Returns the node unchanged when it is not an object or lacks the target property.
|
|
24
|
+
*/
|
|
25
|
+
export function applyDiscriminatorEnum({
|
|
26
|
+
node,
|
|
27
|
+
propertyName,
|
|
28
|
+
values,
|
|
29
|
+
enumName,
|
|
30
|
+
}: {
|
|
31
|
+
node: SchemaNode
|
|
32
|
+
propertyName: string
|
|
33
|
+
values: Array<string>
|
|
34
|
+
enumName?: string
|
|
35
|
+
}): SchemaNode {
|
|
36
|
+
if (node.type !== 'object' || !node.properties?.length) return node
|
|
37
|
+
|
|
38
|
+
const hasProperty = node.properties.some((prop) => prop.name === propertyName)
|
|
39
|
+
if (!hasProperty) return node
|
|
40
|
+
|
|
41
|
+
return createSchema({
|
|
42
|
+
...node,
|
|
43
|
+
properties: node.properties.map((prop) => {
|
|
44
|
+
if (prop.name !== propertyName) return prop
|
|
45
|
+
|
|
46
|
+
const enumSchema: SchemaNode = createSchema({
|
|
47
|
+
type: 'enum' as const,
|
|
48
|
+
primitive: 'string' as const,
|
|
49
|
+
enumValues: values,
|
|
50
|
+
name: enumName,
|
|
51
|
+
readOnly: prop.schema.readOnly,
|
|
52
|
+
writeOnly: prop.schema.writeOnly,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return createProperty({
|
|
56
|
+
...prop,
|
|
57
|
+
schema: enumSchema,
|
|
58
|
+
})
|
|
59
|
+
}),
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Merges consecutive anonymous (`name`-less) `ObjectSchemaNode`s in `members` into a
|
|
65
|
+
* single object by combining their `properties` arrays.
|
|
66
|
+
*
|
|
67
|
+
* Only adjacent pairs are merged — non-object or named nodes act as boundaries.
|
|
68
|
+
* This collapses patterns like `Address & { streetNumber } & { streetName }` into
|
|
69
|
+
* `Address & { streetNumber; streetName }`.
|
|
70
|
+
*/
|
|
71
|
+
export function mergeAdjacentAnonymousObjects(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
72
|
+
return members.reduce<Array<SchemaNode>>((acc, member) => {
|
|
73
|
+
const obj = narrowSchema(member, 'object')
|
|
74
|
+
if (obj && !obj.name) {
|
|
75
|
+
const prev = acc[acc.length - 1]
|
|
76
|
+
const prevObj = prev ? narrowSchema(prev, 'object') : null
|
|
77
|
+
if (prevObj && !prevObj.name) {
|
|
78
|
+
acc[acc.length - 1] = createSchema({
|
|
79
|
+
...prevObj,
|
|
80
|
+
properties: [...(prevObj.properties ?? []), ...(obj.properties ?? [])],
|
|
81
|
+
}) as SchemaNode
|
|
82
|
+
return acc
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
acc.push(member)
|
|
86
|
+
return acc
|
|
87
|
+
}, [])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Simplifies a union member list by removing `enum` nodes whose `primitive` type is
|
|
92
|
+
* already represented by a broader scalar node in the same union.
|
|
93
|
+
*
|
|
94
|
+
* For example `['placed', 'approved'] | string` collapses to `string` because
|
|
95
|
+
* `string` subsumes all string literals. `'' | string` similarly becomes `string`.
|
|
96
|
+
*
|
|
97
|
+
* Only scalar primitives (`string`, `number`, `integer`, `bigint`, `boolean`) are
|
|
98
|
+
* considered — object, array, and ref members are left untouched.
|
|
99
|
+
*/
|
|
100
|
+
export function simplifyUnionMembers(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
101
|
+
const scalarPrimitives = new Set(members.filter((m) => ['string', 'number', 'integer', 'bigint', 'boolean'].includes(m.type)).map((m) => m.type as string))
|
|
102
|
+
if (!scalarPrimitives.size) return members
|
|
103
|
+
|
|
104
|
+
return members.filter((m) => {
|
|
105
|
+
if (m.type !== 'enum') return true
|
|
106
|
+
const prim = m.primitive
|
|
107
|
+
// Keep the enum if its primitive isn't fully subsumed.
|
|
108
|
+
if (!prim) return true
|
|
109
|
+
// `number` subsumes `integer` literals and vice-versa for our purposes.
|
|
110
|
+
if (scalarPrimitives.has(prim)) return false
|
|
111
|
+
if ((prim === 'integer' || prim === 'number') && (scalarPrimitives.has('integer') || scalarPrimitives.has('number'))) return false
|
|
112
|
+
return true
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* `nameMapping`, and calls `resolve` to obtain the `{ name, path }` pair for
|
|
118
|
+
* each import. When `oas` is supplied, only `$ref`s that are resolvable in the
|
|
119
|
+
* spec are included; omit it to skip the existence check.
|
|
120
|
+
*
|
|
121
|
+
* This function is the pure, state-free alternative to `OasParser.getImports`.
|
|
122
|
+
* Because it receives `nameMapping` explicitly it can be called without holding
|
|
123
|
+
* a reference to the parser or the OAS instance.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* // Use adapter state directly — no parser reference needed
|
|
128
|
+
* const imports = getImports({
|
|
129
|
+
* node: schemaNode,
|
|
130
|
+
* nameMapping: adapter.options.nameMapping,
|
|
131
|
+
* resolve: (schemaName) => ({
|
|
132
|
+
* name: schemaManager.getName(schemaName, { type: 'type' }),
|
|
133
|
+
* path: schemaManager.getFile(schemaName).path,
|
|
134
|
+
* }),
|
|
135
|
+
* })
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function getImports({
|
|
139
|
+
node,
|
|
140
|
+
nameMapping,
|
|
141
|
+
resolve,
|
|
142
|
+
}: {
|
|
143
|
+
node: SchemaNode
|
|
144
|
+
nameMapping: Map<string, string>
|
|
145
|
+
resolve: (schemaName: string) => { name: string; path: string } | undefined
|
|
146
|
+
}): Array<KubbFile.Import> {
|
|
147
|
+
return collect<KubbFile.Import>(node, {
|
|
148
|
+
schema(schemaNode): KubbFile.Import | undefined {
|
|
149
|
+
if (schemaNode.type !== 'ref' || !schemaNode.ref) return
|
|
150
|
+
|
|
151
|
+
const rawName = extractRefName(schemaNode.ref)
|
|
152
|
+
|
|
153
|
+
// Apply collision-resolved name if available.
|
|
154
|
+
const schemaName = nameMapping.get(rawName) ?? rawName
|
|
155
|
+
const result = resolve(schemaName)
|
|
156
|
+
if (!result) return
|
|
157
|
+
|
|
158
|
+
return { name: [result.name], path: result.path }
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
}
|