@kubb/adapter-oas 4.36.1 → 5.0.0-alpha.10

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