@kubb/plugin-zod 5.0.0-alpha.3 → 5.0.0-alpha.30
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 +1619 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +418 -4
- package/dist/index.js +1614 -100
- package/dist/index.js.map +1 -1
- package/package.json +6 -34
- package/src/components/Operations.tsx +22 -15
- package/src/components/Zod.tsx +20 -119
- package/src/constants.ts +5 -0
- package/src/generators/zodGenerator.tsx +129 -159
- package/src/generators/zodGeneratorLegacy.tsx +365 -0
- package/src/index.ts +12 -1
- package/src/plugin.ts +102 -148
- package/src/presets.ts +30 -0
- package/src/printers/printerZod.ts +298 -0
- package/src/printers/printerZodMini.ts +273 -0
- package/src/resolvers/resolverZod.ts +61 -0
- package/src/resolvers/resolverZodLegacy.ts +60 -0
- package/src/types.ts +172 -93
- package/src/utils.ts +248 -0
- package/dist/components-B7zUFnAm.cjs +0 -890
- package/dist/components-B7zUFnAm.cjs.map +0 -1
- package/dist/components-eECfXVou.js +0 -842
- package/dist/components-eECfXVou.js.map +0 -1
- package/dist/components.cjs +0 -4
- package/dist/components.d.ts +0 -56
- package/dist/components.js +0 -2
- package/dist/generators-CRKtFRi1.js +0 -290
- package/dist/generators-CRKtFRi1.js.map +0 -1
- package/dist/generators-CzSLRVqQ.cjs +0 -301
- package/dist/generators-CzSLRVqQ.cjs.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -503
- package/dist/generators.js +0 -2
- package/dist/templates/ToZod.source.cjs +0 -7
- package/dist/templates/ToZod.source.cjs.map +0 -1
- package/dist/templates/ToZod.source.d.ts +0 -7
- package/dist/templates/ToZod.source.js +0 -6
- package/dist/templates/ToZod.source.js.map +0 -1
- package/dist/types-D0wsPC6Y.d.ts +0 -172
- package/src/components/index.ts +0 -2
- package/src/generators/index.ts +0 -2
- package/src/generators/operationsGenerator.tsx +0 -50
- package/src/parser.ts +0 -909
- package/src/templates/ToZod.source.ts +0 -4
- package/templates/ToZod.ts +0 -61
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineResolver } from '@kubb/core'
|
|
2
|
+
import type { PluginZod } from '../types.ts'
|
|
3
|
+
import { resolverZod } from './resolverZod.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Legacy resolver for `@kubb/plugin-zod` that reproduces the naming conventions
|
|
7
|
+
* used in Kubb v4. Enable via `compatibilityPreset: 'kubbV4'`
|
|
8
|
+
* (or by composing this resolver manually).
|
|
9
|
+
*
|
|
10
|
+
* Key differences from the default resolver:
|
|
11
|
+
* - Response status types: `<operationId><StatusCode>Schema` (e.g. `createPets201Schema`) instead of `<operationId>Status201Schema`
|
|
12
|
+
* - Default/error responses: `<operationId>ErrorSchema` instead of `<operationId>StatusDefaultSchema`
|
|
13
|
+
* - Request body: `<operationId>MutationRequestSchema` (non-GET) / `<operationId>QueryRequestSchema` (GET)
|
|
14
|
+
* - Combined responses type: `<operationId>MutationSchema` / `<operationId>QuerySchema`
|
|
15
|
+
* - Response union: `<operationId>MutationResponseSchema` / `<operationId>QueryResponseSchema`
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { resolverZodLegacy } from '@kubb/plugin-zod'
|
|
20
|
+
*
|
|
21
|
+
* resolverZodLegacy.resolveResponseStatusName(node, 201) // → 'createPets201Schema'
|
|
22
|
+
* resolverZodLegacy.resolveResponseStatusName(node, 'default') // → 'createPetsErrorSchema'
|
|
23
|
+
* resolverZodLegacy.resolveDataName(node) // → 'createPetsMutationRequestSchema' (POST)
|
|
24
|
+
* resolverZodLegacy.resolveResponsesName(node) // → 'createPetsMutationSchema' (POST)
|
|
25
|
+
* resolverZodLegacy.resolveResponseName(node) // → 'createPetsMutationResponseSchema' (POST)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const resolverZodLegacy = defineResolver<PluginZod>(() => {
|
|
29
|
+
return {
|
|
30
|
+
...resolverZod,
|
|
31
|
+
pluginName: 'plugin-zod',
|
|
32
|
+
resolveResponseStatusName(node, statusCode) {
|
|
33
|
+
if (statusCode === 'default') {
|
|
34
|
+
return this.resolveSchemaName(`${node.operationId} Error`)
|
|
35
|
+
}
|
|
36
|
+
return this.resolveSchemaName(`${node.operationId} ${statusCode}`)
|
|
37
|
+
},
|
|
38
|
+
resolveDataName(node) {
|
|
39
|
+
const suffix = node.method === 'GET' ? 'QueryRequest' : 'MutationRequest'
|
|
40
|
+
return this.resolveSchemaName(`${node.operationId} ${suffix}`)
|
|
41
|
+
},
|
|
42
|
+
resolveResponsesName(node) {
|
|
43
|
+
const suffix = node.method === 'GET' ? 'Query' : 'Mutation'
|
|
44
|
+
return this.resolveSchemaName(`${node.operationId} ${suffix}`)
|
|
45
|
+
},
|
|
46
|
+
resolveResponseName(node) {
|
|
47
|
+
const suffix = node.method === 'GET' ? 'QueryResponse' : 'MutationResponse'
|
|
48
|
+
return this.resolveSchemaName(`${node.operationId} ${suffix}`)
|
|
49
|
+
},
|
|
50
|
+
resolvePathParamsName(node, _param) {
|
|
51
|
+
return this.resolveSchemaName(`${node.operationId} PathParams`)
|
|
52
|
+
},
|
|
53
|
+
resolveQueryParamsName(node, _param) {
|
|
54
|
+
return this.resolveSchemaName(`${node.operationId} QueryParams`)
|
|
55
|
+
},
|
|
56
|
+
resolveHeaderParamsName(node, _param) {
|
|
57
|
+
return this.resolveSchemaName(`${node.operationId} HeaderParams`)
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
})
|
package/src/types.ts
CHANGED
|
@@ -1,22 +1,108 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
1
|
+
import type { OperationParamsResolver } from '@kubb/ast'
|
|
2
|
+
import type { OperationNode, ParameterNode, SchemaNode, StatusCode, Visitor } from '@kubb/ast/types'
|
|
3
|
+
import type {
|
|
4
|
+
CompatibilityPreset,
|
|
5
|
+
Exclude,
|
|
6
|
+
Generator,
|
|
7
|
+
Group,
|
|
8
|
+
Include,
|
|
9
|
+
Output,
|
|
10
|
+
Override,
|
|
11
|
+
PluginFactoryOptions,
|
|
12
|
+
ResolvePathOptions,
|
|
13
|
+
Resolver,
|
|
14
|
+
UserGroup,
|
|
15
|
+
} from '@kubb/core'
|
|
16
|
+
import type { PrinterZodNodes } from './printers/printerZod.ts'
|
|
17
|
+
import type { PrinterZodMiniNodes } from './printers/printerZodMini.ts'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The concrete resolver type for `@kubb/plugin-zod`.
|
|
21
|
+
* Extends the base `Resolver` with zod-specific naming helpers.
|
|
22
|
+
*/
|
|
23
|
+
export type ResolverZod = Resolver &
|
|
24
|
+
OperationParamsResolver & {
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a camelCase schema function name with a `Schema` suffix.
|
|
27
|
+
*/
|
|
28
|
+
resolveSchemaName(this: ResolverZod, name: string): string
|
|
29
|
+
/**
|
|
30
|
+
* Resolves the name for a `z.infer<typeof ...>` type export.
|
|
31
|
+
* Strips the trailing `Schema` suffix (added by `resolveSchemaName`) before PascalCasing.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* resolver.resolveSchemaTypeName('pet) // → 'PetSchema'
|
|
35
|
+
* resolver.resolveSchemaTypeName('addPet200') // → 'AddPet200Schema'
|
|
36
|
+
* resolver.resolveSchemaTypeName('PetName') // → 'PetNameSchema'
|
|
37
|
+
*/
|
|
38
|
+
resolveSchemaTypeName(this: ResolverZod, name: string): string
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the name for a `z.infer<typeof ...>` type export.
|
|
41
|
+
* Strips the trailing `Schema` suffix (added by `resolveSchemaName`) before PascalCasing.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* resolver.resolveTypeName('pet') // → 'Pet'
|
|
45
|
+
* resolver.resolveTypeName('addPet200') // → 'AddPet200'
|
|
46
|
+
* resolver.resolveTypeName('PetName') // → 'PetName'
|
|
47
|
+
*/
|
|
48
|
+
resolveTypeName(this: ResolverZod, name: string): string
|
|
49
|
+
/**
|
|
50
|
+
* Resolves a PascalCase path/file name for the generated output.
|
|
51
|
+
*/
|
|
52
|
+
resolvePathName(this: ResolverZod, name: string, type?: 'file' | 'function' | 'type' | 'const'): string
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the name for an operation response by status code.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* resolver.resolveResponseStatusName(node, 200) // → 'listPetsStatus200Schema'
|
|
58
|
+
*/
|
|
59
|
+
resolveResponseStatusName(this: ResolverZod, node: OperationNode, statusCode: StatusCode): string
|
|
60
|
+
/**
|
|
61
|
+
* Resolves the name for the collection of all operation responses.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* resolver.resolveResponsesName(node) // → 'listPetsResponsesSchema'
|
|
65
|
+
*/
|
|
66
|
+
resolveResponsesName(this: ResolverZod, node: OperationNode): string
|
|
67
|
+
/**
|
|
68
|
+
* Resolves the name for the union of all operation responses.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* resolver.resolveResponseName(node) // → 'listPetsResponseSchema'
|
|
72
|
+
*/
|
|
73
|
+
resolveResponseName(this: ResolverZod, node: OperationNode): string
|
|
74
|
+
/**
|
|
75
|
+
* Resolves the name for an operation's grouped path parameters type.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* resolver.resolvePathParamsName(node, param) // → 'deletePetPathPetIdSchema'
|
|
79
|
+
*/
|
|
80
|
+
resolvePathParamsName(this: ResolverZod, node: OperationNode, param: ParameterNode): string
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the name for an operation's grouped query parameters type.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* resolver.resolveQueryParamsName(node, param) // → 'findPetsByStatusQueryStatusSchema'
|
|
86
|
+
*/
|
|
87
|
+
resolveQueryParamsName(this: ResolverZod, node: OperationNode, param: ParameterNode): string
|
|
88
|
+
/**
|
|
89
|
+
* Resolves the name for an operation's grouped header parameters type.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* resolver.resolveHeaderParamsName(node, param) // → 'deletePetHeaderApiKeySchema'
|
|
93
|
+
*/
|
|
94
|
+
resolveHeaderParamsName(this: ResolverZod, node: OperationNode, param: ParameterNode): string
|
|
95
|
+
}
|
|
5
96
|
|
|
6
97
|
export type Options = {
|
|
7
98
|
/**
|
|
8
99
|
* @default 'zod'
|
|
9
100
|
*/
|
|
10
|
-
output?: Output
|
|
11
|
-
/**
|
|
12
|
-
* Define which contentType should be used.
|
|
13
|
-
* By default, the first JSON valid mediaType is used
|
|
14
|
-
*/
|
|
15
|
-
contentType?: contentType
|
|
101
|
+
output?: Output
|
|
16
102
|
/**
|
|
17
103
|
* Group the Zod schemas based on the provided name.
|
|
18
104
|
*/
|
|
19
|
-
group?:
|
|
105
|
+
group?: UserGroup
|
|
20
106
|
/**
|
|
21
107
|
* Array containing exclude parameters to exclude/skip tags/operations/methods/paths.
|
|
22
108
|
*/
|
|
@@ -36,8 +122,7 @@ export type Options = {
|
|
|
36
122
|
* Path is used as-is; relative paths are based on the generated file location.
|
|
37
123
|
* @default 'zod'
|
|
38
124
|
*/
|
|
39
|
-
importPath?: string
|
|
40
|
-
|
|
125
|
+
importPath?: 'zod' | 'zod/mini' | (string & {})
|
|
41
126
|
/**
|
|
42
127
|
* Choose to use date or datetime as JavaScript Date instead of string.
|
|
43
128
|
* - false falls back to a simple z.string() format.
|
|
@@ -46,33 +131,8 @@ export type Options = {
|
|
|
46
131
|
* - 'stringLocal' uses z.string().datetime({ local: true }) for local datetime validation.
|
|
47
132
|
* - 'date' uses z.date() for JavaScript Date objects.
|
|
48
133
|
* @default 'string'
|
|
49
|
-
* @note 'stringOffset' will become the default in Kubb v3.
|
|
50
134
|
*/
|
|
51
135
|
dateType?: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
|
|
52
|
-
/**
|
|
53
|
-
* Choose to use `number` or `bigint` for integer fields with `int64` format.
|
|
54
|
-
* - 'number' uses the JavaScript `number` type (matches JSON.parse() runtime behavior).
|
|
55
|
-
* - 'bigint' uses the JavaScript `bigint` type (accurate for values exceeding Number.MAX_SAFE_INTEGER).
|
|
56
|
-
* @note in v5 of Kubb 'bigint' will become the default to better align with OpenAPI's int64 specification.
|
|
57
|
-
* @default 'number'
|
|
58
|
-
*/
|
|
59
|
-
integerType?: 'number' | 'bigint'
|
|
60
|
-
/**
|
|
61
|
-
* Which type to use when the Swagger/OpenAPI file is not providing more information.
|
|
62
|
-
* - 'any' allows any value.
|
|
63
|
-
* - 'unknown' requires type narrowing before use.
|
|
64
|
-
* - 'void' represents no value.
|
|
65
|
-
* @default 'any'
|
|
66
|
-
*/
|
|
67
|
-
unknownType?: 'any' | 'unknown' | 'void'
|
|
68
|
-
/**
|
|
69
|
-
* Which type to use for empty schema values.
|
|
70
|
-
* - 'any' allows any value.
|
|
71
|
-
* - 'unknown' requires type narrowing before use.
|
|
72
|
-
* - 'void' represents no value.
|
|
73
|
-
* @default `unknownType`
|
|
74
|
-
*/
|
|
75
|
-
emptySchemaType?: 'any' | 'unknown' | 'void'
|
|
76
136
|
/**
|
|
77
137
|
* Use TypeScript(`@kubb/plugin-ts`) to add type annotation.
|
|
78
138
|
*/
|
|
@@ -82,91 +142,110 @@ export type Options = {
|
|
|
82
142
|
*/
|
|
83
143
|
inferred?: boolean
|
|
84
144
|
/**
|
|
85
|
-
* Use of z.coerce.string() instead of z.string()
|
|
86
|
-
*
|
|
145
|
+
* Use of z.coerce.string() instead of z.string().
|
|
146
|
+
* Can also be an object to enable coercion for dates, strings, and numbers.
|
|
87
147
|
*/
|
|
88
|
-
coercion?:
|
|
89
|
-
| boolean
|
|
90
|
-
| {
|
|
91
|
-
dates?: boolean
|
|
92
|
-
strings?: boolean
|
|
93
|
-
numbers?: boolean
|
|
94
|
-
}
|
|
95
|
-
operations?: boolean
|
|
96
|
-
mapper?: Record<string, string>
|
|
97
|
-
transformers?: {
|
|
98
|
-
/**
|
|
99
|
-
* Customize the names based on the type that is provided by the plugin.
|
|
100
|
-
*/
|
|
101
|
-
name?: (name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
|
|
102
|
-
/**
|
|
103
|
-
* Receive schema and baseName(propertyName) and return FakerMeta array
|
|
104
|
-
* TODO TODO add docs
|
|
105
|
-
* @beta
|
|
106
|
-
*/
|
|
107
|
-
schema?: (
|
|
108
|
-
props: {
|
|
109
|
-
schema: SchemaObject | null
|
|
110
|
-
name: string | null
|
|
111
|
-
parentName: string | null
|
|
112
|
-
},
|
|
113
|
-
defaultSchemas: Schema[],
|
|
114
|
-
) => Schema[] | undefined
|
|
115
|
-
}
|
|
148
|
+
coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
|
|
116
149
|
/**
|
|
117
|
-
*
|
|
118
|
-
* - '3' uses Zod v3.x syntax and features.
|
|
119
|
-
* - '4' uses Zod v4.x syntax and features.
|
|
120
|
-
* @default '3'
|
|
150
|
+
* Generate operation-level schemas (grouped by operationId).
|
|
121
151
|
*/
|
|
122
|
-
|
|
152
|
+
operations?: boolean
|
|
123
153
|
/**
|
|
124
154
|
* Which Zod GUID validator to use for OpenAPI `format: uuid`.
|
|
125
155
|
* - 'uuid' uses UUID validation.
|
|
126
|
-
* - 'guid' uses GUID validation
|
|
156
|
+
* - 'guid' uses GUID validation.
|
|
127
157
|
* @default 'uuid'
|
|
128
158
|
*/
|
|
129
159
|
guidType?: 'uuid' | 'guid'
|
|
130
160
|
/**
|
|
131
161
|
* Use Zod Mini's functional API for better tree-shaking support.
|
|
132
|
-
* When enabled, generates functional syntax (e.g., `z.optional(z.string())`)
|
|
133
|
-
*
|
|
162
|
+
* When enabled, generates functional syntax (e.g., `z.optional(z.string())`)
|
|
163
|
+
* instead of chainable methods (e.g., `z.string().optional()`).
|
|
164
|
+
* When `mini: true`, `importPath` will default to 'zod/mini'.
|
|
134
165
|
* @default false
|
|
135
166
|
*/
|
|
136
167
|
mini?: boolean
|
|
137
168
|
/**
|
|
138
|
-
* Callback function to wrap the output of the generated zod schema
|
|
169
|
+
* Callback function to wrap the output of the generated zod schema.
|
|
139
170
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* while going from openapi -> zod -> openapi
|
|
171
|
+
* Useful for edge cases like adding `.openapi()` metadata or wrapping
|
|
172
|
+
* schemas with extension helpers (openapi -> zod -> openapi round-trips).
|
|
143
173
|
*/
|
|
144
|
-
wrapOutput?: (arg: { output: string; schema:
|
|
174
|
+
wrapOutput?: (arg: { output: string; schema: SchemaNode }) => string | undefined
|
|
145
175
|
/**
|
|
146
|
-
*
|
|
176
|
+
* How to style your params, by default no casing is applied
|
|
177
|
+
* - 'camelcase' uses camelCase for pathParams, queryParams and headerParams property names
|
|
178
|
+
* @default undefined
|
|
179
|
+
*/
|
|
180
|
+
paramsCasing?: 'camelcase'
|
|
181
|
+
/**
|
|
182
|
+
* Define additional generators next to the zod generators.
|
|
147
183
|
*/
|
|
148
184
|
generators?: Array<Generator<PluginZod>>
|
|
185
|
+
/**
|
|
186
|
+
* Compatibility preset to ease migration from previous Kubb versions.
|
|
187
|
+
*/
|
|
188
|
+
compatibilityPreset?: CompatibilityPreset
|
|
189
|
+
/**
|
|
190
|
+
* A single resolver whose methods override the default resolver's naming conventions.
|
|
191
|
+
* When a method returns `null` or `undefined`, the default resolver's result is used instead.
|
|
192
|
+
*/
|
|
193
|
+
resolver?: Partial<ResolverZod> & ThisType<ResolverZod>
|
|
194
|
+
/**
|
|
195
|
+
* Override individual printer node handlers to customize rendering of specific schema types.
|
|
196
|
+
*
|
|
197
|
+
* Each key is a `SchemaType` (e.g. `'date'`, `'string'`). The function replaces the
|
|
198
|
+
* built-in handler for that type. Use `this.transform` to recurse into nested schema nodes.
|
|
199
|
+
* When `mini: true`, the overrides apply to the Zod Mini printer.
|
|
200
|
+
*
|
|
201
|
+
* @example Override the `date` node to use `z.string().date()`
|
|
202
|
+
* ```ts
|
|
203
|
+
* pluginZod({
|
|
204
|
+
* printer: {
|
|
205
|
+
* nodes: {
|
|
206
|
+
* date(node) {
|
|
207
|
+
* return 'z.string().date()'
|
|
208
|
+
* },
|
|
209
|
+
* },
|
|
210
|
+
* },
|
|
211
|
+
* })
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
printer?: {
|
|
215
|
+
nodes?: PrinterZodNodes | PrinterZodMiniNodes
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* A single AST visitor applied to each SchemaNode/OperationNode before printing.
|
|
219
|
+
* When a visitor method returns `null` or `undefined`, the preset transformer's result is used instead.
|
|
220
|
+
*/
|
|
221
|
+
transformer?: Visitor
|
|
149
222
|
}
|
|
150
223
|
|
|
151
224
|
type ResolvedOptions = {
|
|
152
|
-
output: Output
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
225
|
+
output: Output
|
|
226
|
+
exclude: Array<Exclude>
|
|
227
|
+
include: Array<Include> | undefined
|
|
228
|
+
override: Array<Override<ResolvedOptions>>
|
|
229
|
+
group: Group | undefined
|
|
156
230
|
dateType: NonNullable<Options['dateType']>
|
|
157
|
-
integerType: NonNullable<Options['integerType']>
|
|
158
|
-
unknownType: NonNullable<Options['unknownType']>
|
|
159
|
-
emptySchemaType: NonNullable<Options['emptySchemaType']>
|
|
160
231
|
typed: NonNullable<Options['typed']>
|
|
161
232
|
inferred: NonNullable<Options['inferred']>
|
|
162
|
-
mapper: NonNullable<Options['mapper']>
|
|
163
233
|
importPath: NonNullable<Options['importPath']>
|
|
164
234
|
coercion: NonNullable<Options['coercion']>
|
|
165
235
|
operations: NonNullable<Options['operations']>
|
|
166
|
-
wrapOutput: Options['wrapOutput']
|
|
167
|
-
version: NonNullable<Options['version']>
|
|
168
236
|
guidType: NonNullable<Options['guidType']>
|
|
169
237
|
mini: NonNullable<Options['mini']>
|
|
238
|
+
wrapOutput: Options['wrapOutput']
|
|
239
|
+
paramsCasing: Options['paramsCasing']
|
|
240
|
+
printer: Options['printer']
|
|
170
241
|
}
|
|
171
242
|
|
|
172
|
-
export type PluginZod = PluginFactoryOptions<'plugin-zod', Options, ResolvedOptions, never, ResolvePathOptions>
|
|
243
|
+
export type PluginZod = PluginFactoryOptions<'plugin-zod', Options, ResolvedOptions, never, ResolvePathOptions, ResolverZod>
|
|
244
|
+
|
|
245
|
+
declare global {
|
|
246
|
+
namespace Kubb {
|
|
247
|
+
interface PluginRegistry {
|
|
248
|
+
'plugin-zod': PluginZod
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { stringify, toRegExpString } from '@internals/utils'
|
|
2
|
+
import { createProperty, createSchema, extractRefName } from '@kubb/ast'
|
|
3
|
+
import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
|
|
4
|
+
import type { PluginZod, ResolverZod } from './types.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns `true` when the given coercion option enables coercion for the specified type.
|
|
8
|
+
*/
|
|
9
|
+
export function shouldCoerce(coercion: PluginZod['resolvedOptions']['coercion'] | undefined, type: 'dates' | 'strings' | 'numbers'): boolean {
|
|
10
|
+
if (coercion === undefined || coercion === false) return false
|
|
11
|
+
if (coercion === true) return true
|
|
12
|
+
|
|
13
|
+
return !!coercion[type]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Collects all resolved schema names for an operation's parameters and responses
|
|
18
|
+
* into a single lookup object, useful for building imports and type references.
|
|
19
|
+
*/
|
|
20
|
+
export function buildSchemaNames(node: OperationNode, { params, resolver }: { params: Array<ParameterNode>; resolver: ResolverZod }) {
|
|
21
|
+
const pathParam = params.find((p) => p.in === 'path')
|
|
22
|
+
const queryParam = params.find((p) => p.in === 'query')
|
|
23
|
+
const headerParam = params.find((p) => p.in === 'header')
|
|
24
|
+
|
|
25
|
+
const responses: Record<number | string, string> = {}
|
|
26
|
+
const errors: Record<number | string, string> = {}
|
|
27
|
+
|
|
28
|
+
for (const res of node.responses) {
|
|
29
|
+
const name = resolver.resolveResponseStatusName(node, res.statusCode)
|
|
30
|
+
const statusNum = Number(res.statusCode)
|
|
31
|
+
|
|
32
|
+
if (!Number.isNaN(statusNum)) {
|
|
33
|
+
responses[statusNum] = name
|
|
34
|
+
if (statusNum >= 400) {
|
|
35
|
+
errors[statusNum] = name
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
responses['default'] = resolver.resolveResponseName(node)
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
request: node.requestBody?.schema ? resolver.resolveDataName(node) : undefined,
|
|
44
|
+
parameters: {
|
|
45
|
+
path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : undefined,
|
|
46
|
+
query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : undefined,
|
|
47
|
+
header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : undefined,
|
|
48
|
+
},
|
|
49
|
+
responses,
|
|
50
|
+
errors,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Format a default value as a code-level literal.
|
|
56
|
+
* Objects become `{}`, primitives become their string representation, strings are quoted.
|
|
57
|
+
*/
|
|
58
|
+
export function formatDefault(value: unknown): string {
|
|
59
|
+
if (typeof value === 'string') return stringify(value)
|
|
60
|
+
if (typeof value === 'object' && value !== null) return '{}'
|
|
61
|
+
|
|
62
|
+
return String(value ?? '')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format a primitive enum/literal value.
|
|
67
|
+
* Strings are quoted; numbers and booleans are emitted raw.
|
|
68
|
+
*/
|
|
69
|
+
export function formatLiteral(v: string | number | boolean): string {
|
|
70
|
+
if (typeof v === 'string') return stringify(v)
|
|
71
|
+
|
|
72
|
+
return String(v)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type NumericConstraints = {
|
|
76
|
+
min?: number
|
|
77
|
+
max?: number
|
|
78
|
+
exclusiveMinimum?: number
|
|
79
|
+
exclusiveMaximum?: number
|
|
80
|
+
multipleOf?: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type LengthConstraints = {
|
|
84
|
+
min?: number
|
|
85
|
+
max?: number
|
|
86
|
+
pattern?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type ModifierOptions = {
|
|
90
|
+
value: string
|
|
91
|
+
nullable?: boolean
|
|
92
|
+
optional?: boolean
|
|
93
|
+
nullish?: boolean
|
|
94
|
+
defaultValue?: unknown
|
|
95
|
+
description?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
|
|
100
|
+
* using the standard chainable Zod v4 API.
|
|
101
|
+
*/
|
|
102
|
+
export function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }: NumericConstraints): string {
|
|
103
|
+
return [
|
|
104
|
+
min !== undefined ? `.min(${min})` : '',
|
|
105
|
+
max !== undefined ? `.max(${max})` : '',
|
|
106
|
+
exclusiveMinimum !== undefined ? `.gt(${exclusiveMinimum})` : '',
|
|
107
|
+
exclusiveMaximum !== undefined ? `.lt(${exclusiveMaximum})` : '',
|
|
108
|
+
multipleOf !== undefined ? `.multipleOf(${multipleOf})` : '',
|
|
109
|
+
].join('')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build `.min()` / `.max()` / `.regex()` chains for strings/arrays
|
|
114
|
+
* using the standard chainable Zod v4 API.
|
|
115
|
+
*/
|
|
116
|
+
export function lengthConstraints({ min, max, pattern }: LengthConstraints): string {
|
|
117
|
+
return [
|
|
118
|
+
min !== undefined ? `.min(${min})` : '',
|
|
119
|
+
max !== undefined ? `.max(${max})` : '',
|
|
120
|
+
pattern !== undefined ? `.regex(${toRegExpString(pattern, null)})` : '',
|
|
121
|
+
].join('')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
|
|
126
|
+
*/
|
|
127
|
+
export function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }: NumericConstraints): string {
|
|
128
|
+
const checks: string[] = []
|
|
129
|
+
if (min !== undefined) checks.push(`z.minimum(${min})`)
|
|
130
|
+
if (max !== undefined) checks.push(`z.maximum(${max})`)
|
|
131
|
+
if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
|
|
132
|
+
if (exclusiveMaximum !== undefined) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`)
|
|
133
|
+
if (multipleOf !== undefined) checks.push(`z.multipleOf(${multipleOf})`)
|
|
134
|
+
return checks.length ? `.check(${checks.join(', ')})` : ''
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
|
|
139
|
+
*/
|
|
140
|
+
export function lengthChecksMini({ min, max, pattern }: LengthConstraints): string {
|
|
141
|
+
const checks: string[] = []
|
|
142
|
+
if (min !== undefined) checks.push(`z.minLength(${min})`)
|
|
143
|
+
if (max !== undefined) checks.push(`z.maxLength(${max})`)
|
|
144
|
+
if (pattern !== undefined) checks.push(`z.regex(${toRegExpString(pattern, null)})`)
|
|
145
|
+
return checks.length ? `.check(${checks.join(', ')})` : ''
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Apply nullable / optional / nullish modifiers and an optional `.describe()` call
|
|
150
|
+
* to a schema value string using the chainable Zod v4 API.
|
|
151
|
+
*/
|
|
152
|
+
export function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }: ModifierOptions): string {
|
|
153
|
+
let result = value
|
|
154
|
+
if (nullish || (nullable && optional)) {
|
|
155
|
+
result = `${result}.nullish()`
|
|
156
|
+
} else if (optional) {
|
|
157
|
+
result = `${result}.optional()`
|
|
158
|
+
} else if (nullable) {
|
|
159
|
+
result = `${result}.nullable()`
|
|
160
|
+
}
|
|
161
|
+
if (defaultValue !== undefined) {
|
|
162
|
+
result = `${result}.default(${formatDefault(defaultValue)})`
|
|
163
|
+
}
|
|
164
|
+
if (description) {
|
|
165
|
+
result = `${result}.describe(${stringify(description)})`
|
|
166
|
+
}
|
|
167
|
+
return result
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
|
|
172
|
+
* (`z.nullable()`, `z.optional()`, `z.nullish()`).
|
|
173
|
+
*/
|
|
174
|
+
export function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }: Omit<ModifierOptions, 'description'>): string {
|
|
175
|
+
let result = value
|
|
176
|
+
if (nullish) {
|
|
177
|
+
result = `z.nullish(${result})`
|
|
178
|
+
} else {
|
|
179
|
+
if (nullable) {
|
|
180
|
+
result = `z.nullable(${result})`
|
|
181
|
+
}
|
|
182
|
+
if (optional) {
|
|
183
|
+
result = `z.optional(${result})`
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (defaultValue !== undefined) {
|
|
187
|
+
result = `z._default(${result}, ${formatDefault(defaultValue)})`
|
|
188
|
+
}
|
|
189
|
+
return result
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Returns true when the schema tree contains a self-referential `$ref`
|
|
194
|
+
* whose resolved name matches `schemaName`.
|
|
195
|
+
*
|
|
196
|
+
* A `visited` set prevents infinite recursion on circular schema graphs.
|
|
197
|
+
*/
|
|
198
|
+
export function containsSelfRef(
|
|
199
|
+
node: SchemaNode,
|
|
200
|
+
{ schemaName, resolver, visited = new Set() }: { schemaName: string; resolver: ResolverZod | undefined; visited?: Set<SchemaNode> },
|
|
201
|
+
): boolean {
|
|
202
|
+
if (visited.has(node)) return false
|
|
203
|
+
visited.add(node)
|
|
204
|
+
|
|
205
|
+
if (node.type === 'ref' && node.ref) {
|
|
206
|
+
const rawName = extractRefName(node.ref) ?? node.name
|
|
207
|
+
const resolved = rawName ? (resolver?.default(rawName, 'function') ?? rawName) : node.name
|
|
208
|
+
return resolved === schemaName
|
|
209
|
+
}
|
|
210
|
+
if (node.type === 'object') {
|
|
211
|
+
if (node.properties?.some((p) => containsSelfRef(p.schema, { schemaName, resolver, visited }))) return true
|
|
212
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
213
|
+
return containsSelfRef(node.additionalProperties, { schemaName, resolver, visited })
|
|
214
|
+
}
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
if (node.type === 'array' || node.type === 'tuple') {
|
|
218
|
+
return node.items?.some((item) => containsSelfRef(item, { schemaName, resolver, visited })) ?? false
|
|
219
|
+
}
|
|
220
|
+
if (node.type === 'union' || node.type === 'intersection') {
|
|
221
|
+
return node.members?.some((m) => containsSelfRef(m, { schemaName, resolver, visited })) ?? false
|
|
222
|
+
}
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
type BuildGroupedParamsSchemaOptions = {
|
|
227
|
+
params: Array<ParameterNode>
|
|
228
|
+
optional?: boolean
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Builds an `object` schema node grouping the given parameter nodes.
|
|
233
|
+
* The `primitive: 'object'` marker ensures the Zod printer emits `z.object(…)` rather than a record.
|
|
234
|
+
*/
|
|
235
|
+
export function buildGroupedParamsSchema({ params, optional }: BuildGroupedParamsSchemaOptions): SchemaNode {
|
|
236
|
+
return createSchema({
|
|
237
|
+
type: 'object',
|
|
238
|
+
optional,
|
|
239
|
+
primitive: 'object',
|
|
240
|
+
properties: params.map((param) =>
|
|
241
|
+
createProperty({
|
|
242
|
+
name: param.name,
|
|
243
|
+
required: param.required,
|
|
244
|
+
schema: param.schema,
|
|
245
|
+
}),
|
|
246
|
+
),
|
|
247
|
+
})
|
|
248
|
+
}
|