@kubb/adapter-oas 5.0.0-alpha.2 → 5.0.0-alpha.3

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/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
+ }