@kubb/plugin-zod 5.0.0-beta.99 → 5.0.0
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 +275 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.js +276 -106
- package/dist/index.js.map +1 -1
- package/package.json +3 -6
package/dist/index.d.ts
CHANGED
|
@@ -238,21 +238,21 @@ type ResolverZod = Resolver & {
|
|
|
238
238
|
* Resolves the name for an operation's grouped path parameters schema.
|
|
239
239
|
*
|
|
240
240
|
* @example Path parameters names
|
|
241
|
-
* `resolver.param.path(node, param) // → '
|
|
241
|
+
* `resolver.param.path(node, param) // → 'deletePetPathSchema'`
|
|
242
242
|
*/
|
|
243
243
|
path(node: ast.OperationNode, param: ast.ParameterNode): string;
|
|
244
244
|
/**
|
|
245
245
|
* Resolves the name for an operation's grouped query parameters schema.
|
|
246
246
|
*
|
|
247
247
|
* @example Query parameters names
|
|
248
|
-
* `resolver.param.query(node, param) // → '
|
|
248
|
+
* `resolver.param.query(node, param) // → 'findPetsByStatusQuerySchema'`
|
|
249
249
|
*/
|
|
250
250
|
query(node: ast.OperationNode, param: ast.ParameterNode): string;
|
|
251
251
|
/**
|
|
252
252
|
* Resolves the name for an operation's grouped header parameters schema.
|
|
253
253
|
*
|
|
254
254
|
* @example Header parameters names
|
|
255
|
-
* `resolver.param.headers(node, param) // → '
|
|
255
|
+
* `resolver.param.headers(node, param) // → 'deletePetHeadersSchema'`
|
|
256
256
|
*/
|
|
257
257
|
headers(node: ast.OperationNode, param: ast.ParameterNode): string;
|
|
258
258
|
};
|
|
@@ -297,6 +297,15 @@ type ResolverZod = Resolver & {
|
|
|
297
297
|
* `resolver.response.error(node) // → 'listPetsErrorSchema'`
|
|
298
298
|
*/
|
|
299
299
|
error(node: ast.OperationNode): string;
|
|
300
|
+
/**
|
|
301
|
+
* Resolves the inferred type name for an operation's combined `{ body, path, query, headers }`
|
|
302
|
+
* options object. Only meaningful when `inferred: true`, since the schema and type this name
|
|
303
|
+
* points to are generated only in that case.
|
|
304
|
+
*
|
|
305
|
+
* @example Options type names
|
|
306
|
+
* `resolver.response.options(node) // → 'ListPetsOptionsSchemaType'`
|
|
307
|
+
*/
|
|
308
|
+
options(node: ast.OperationNode): string;
|
|
300
309
|
};
|
|
301
310
|
};
|
|
302
311
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,180 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
-
import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
|
|
2
|
+
import { Resolver, ast, containsCircularRef, createResolver, defineGenerator, definePlugin, extractRefName, syncSchemaRef } from "kubb/kit";
|
|
3
3
|
import { Const, File, Type, jsxRenderer } from "kubb/jsx";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
|
|
5
|
+
//#region ../../internals/shared/src/params.ts
|
|
6
|
+
/**
|
|
7
|
+
* Drops parameters that share the same name, keeping the first.
|
|
8
|
+
*
|
|
9
|
+
* A malformed spec can declare the same parameter name twice within one `in` location. Both would
|
|
10
|
+
* resolve to the same output property, so emitting both would yield an object type with a duplicate
|
|
11
|
+
* member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
|
|
12
|
+
* parameter names flow through unchanged, so no two distinct names ever collide here anymore.
|
|
13
|
+
*/
|
|
14
|
+
function dedupeParams(params) {
|
|
15
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16
|
+
return params.filter((param) => {
|
|
17
|
+
if (seen.has(param.name)) return false;
|
|
18
|
+
seen.add(param.name);
|
|
19
|
+
return true;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region ../../internals/shared/src/operation.ts
|
|
24
|
+
/**
|
|
25
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
26
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
27
|
+
*/
|
|
28
|
+
function getContentTypeSuffix(contentType) {
|
|
29
|
+
const baseType = contentType.split(";")[0].trim();
|
|
30
|
+
if (baseType === "application/json") return "Json";
|
|
31
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
32
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
33
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
34
|
+
if (parts.length === 0) return "Unknown";
|
|
35
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
39
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
40
|
+
*/
|
|
41
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
42
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
43
|
+
return baseName + suffix;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
47
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
48
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
49
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
50
|
+
*/
|
|
51
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
52
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
53
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
54
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
55
|
+
let suffix = baseSuffix;
|
|
56
|
+
let name = getPerContentTypeName(baseName, suffix);
|
|
57
|
+
let counter = 2;
|
|
58
|
+
while (usedNames.has(name)) {
|
|
59
|
+
suffix = `${baseSuffix}${counter++}`;
|
|
60
|
+
name = getPerContentTypeName(baseName, suffix);
|
|
61
|
+
}
|
|
62
|
+
usedNames.add(name);
|
|
63
|
+
return {
|
|
64
|
+
name,
|
|
65
|
+
suffix,
|
|
66
|
+
schema: entry.schema,
|
|
67
|
+
keysToOmit: entry.keysToOmit,
|
|
68
|
+
contentType: entry.contentType
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
|
|
73
|
+
/**
|
|
74
|
+
* Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
|
|
75
|
+
* group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
|
|
76
|
+
* (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
|
|
77
|
+
* same parameters once per plugin.
|
|
78
|
+
*/
|
|
79
|
+
function getOperationParameters(node) {
|
|
80
|
+
const cached = operationParameterGroupsByNode.get(node);
|
|
81
|
+
if (cached) return cached;
|
|
82
|
+
const groups = {
|
|
83
|
+
path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
|
|
84
|
+
query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
|
|
85
|
+
header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
|
|
86
|
+
cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
|
|
87
|
+
};
|
|
88
|
+
operationParameterGroupsByNode.set(node, groups);
|
|
89
|
+
return groups;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Builds the combined `{ body, path, query, headers }` options object schema for an operation,
|
|
93
|
+
* referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
|
|
94
|
+
* `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
|
|
95
|
+
* shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
|
|
96
|
+
* printer to emit `z.object(…)` rather than a record.
|
|
97
|
+
*/
|
|
98
|
+
function buildOptionsSchema(node, resolver) {
|
|
99
|
+
const { path, query, header } = getOperationParameters(node);
|
|
100
|
+
const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
101
|
+
const createNever = () => ast.factory.createSchema({
|
|
102
|
+
type: "never",
|
|
103
|
+
primitive: void 0,
|
|
104
|
+
optional: true
|
|
105
|
+
});
|
|
106
|
+
const groups = [
|
|
107
|
+
{
|
|
108
|
+
name: "path",
|
|
109
|
+
params: path,
|
|
110
|
+
resolve: resolver.param.path
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "query",
|
|
114
|
+
params: query,
|
|
115
|
+
resolve: resolver.param.query
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "headers",
|
|
119
|
+
params: header,
|
|
120
|
+
resolve: resolver.param.headers
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
return ast.factory.createSchema({
|
|
124
|
+
type: "object",
|
|
125
|
+
primitive: "object",
|
|
126
|
+
deprecated: node.deprecated,
|
|
127
|
+
properties: [ast.factory.createProperty({
|
|
128
|
+
name: "body",
|
|
129
|
+
required: hasBody,
|
|
130
|
+
schema: hasBody ? ast.factory.createSchema({
|
|
131
|
+
type: "ref",
|
|
132
|
+
name: resolver.response.body(node)
|
|
133
|
+
}) : createNever()
|
|
134
|
+
}), ...groups.map(({ name, params, resolve }) => {
|
|
135
|
+
const required = params.some((param) => param.required);
|
|
136
|
+
return ast.factory.createProperty({
|
|
137
|
+
name,
|
|
138
|
+
required,
|
|
139
|
+
schema: params.length > 0 ? ast.factory.createSchema({
|
|
140
|
+
type: "ref",
|
|
141
|
+
name: resolve.call(resolver.param, node, params[0]),
|
|
142
|
+
optional: !required
|
|
143
|
+
}) : createNever()
|
|
144
|
+
});
|
|
145
|
+
})]
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function getStatusCodeNumber(statusCode) {
|
|
149
|
+
const code = Number(statusCode);
|
|
150
|
+
return Number.isNaN(code) ? null : code;
|
|
151
|
+
}
|
|
152
|
+
function isSuccessStatusCode(statusCode) {
|
|
153
|
+
const code = getStatusCodeNumber(statusCode);
|
|
154
|
+
return code !== null && code >= 200 && code < 300;
|
|
155
|
+
}
|
|
156
|
+
function getSuccessResponses(responses) {
|
|
157
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region ../../internals/shared/src/adapter.ts
|
|
161
|
+
/**
|
|
162
|
+
* Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
|
|
163
|
+
* so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
|
|
164
|
+
*
|
|
165
|
+
* Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
|
|
166
|
+
* clear, actionable error at the point of use.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* const { dateType } = getOasAdapter(ctx.adapter).options
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
function getOasAdapter(adapter) {
|
|
174
|
+
if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
|
|
175
|
+
return adapter;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
5
178
|
//#region ../../internals/utils/src/casing.ts
|
|
6
179
|
/**
|
|
7
180
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -346,85 +519,6 @@ function toFilePath(name, caseLast = camelCase) {
|
|
|
346
519
|
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
347
520
|
}
|
|
348
521
|
//#endregion
|
|
349
|
-
//#region ../../internals/shared/src/operation.ts
|
|
350
|
-
/**
|
|
351
|
-
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
352
|
-
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
353
|
-
*/
|
|
354
|
-
function getContentTypeSuffix(contentType) {
|
|
355
|
-
const baseType = contentType.split(";")[0].trim();
|
|
356
|
-
if (baseType === "application/json") return "Json";
|
|
357
|
-
if (baseType === "multipart/form-data") return "FormData";
|
|
358
|
-
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
359
|
-
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
360
|
-
if (parts.length === 0) return "Unknown";
|
|
361
|
-
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
365
|
-
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
366
|
-
*/
|
|
367
|
-
function getPerContentTypeName(baseName, suffix) {
|
|
368
|
-
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
369
|
-
return baseName + suffix;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
373
|
-
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
374
|
-
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
375
|
-
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
376
|
-
*/
|
|
377
|
-
function resolveContentTypeVariants(entries, baseName) {
|
|
378
|
-
const usedNames = /* @__PURE__ */ new Set();
|
|
379
|
-
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
380
|
-
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
381
|
-
let suffix = baseSuffix;
|
|
382
|
-
let name = getPerContentTypeName(baseName, suffix);
|
|
383
|
-
let counter = 2;
|
|
384
|
-
while (usedNames.has(name)) {
|
|
385
|
-
suffix = `${baseSuffix}${counter++}`;
|
|
386
|
-
name = getPerContentTypeName(baseName, suffix);
|
|
387
|
-
}
|
|
388
|
-
usedNames.add(name);
|
|
389
|
-
return {
|
|
390
|
-
name,
|
|
391
|
-
suffix,
|
|
392
|
-
schema: entry.schema,
|
|
393
|
-
keysToOmit: entry.keysToOmit,
|
|
394
|
-
contentType: entry.contentType
|
|
395
|
-
};
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
function getStatusCodeNumber(statusCode) {
|
|
399
|
-
const code = Number(statusCode);
|
|
400
|
-
return Number.isNaN(code) ? null : code;
|
|
401
|
-
}
|
|
402
|
-
function isSuccessStatusCode(statusCode) {
|
|
403
|
-
const code = getStatusCodeNumber(statusCode);
|
|
404
|
-
return code !== null && code >= 200 && code < 300;
|
|
405
|
-
}
|
|
406
|
-
function getSuccessResponses(responses) {
|
|
407
|
-
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
408
|
-
}
|
|
409
|
-
//#endregion
|
|
410
|
-
//#region ../../internals/shared/src/adapter.ts
|
|
411
|
-
/**
|
|
412
|
-
* Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
|
|
413
|
-
* so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
|
|
414
|
-
*
|
|
415
|
-
* Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
|
|
416
|
-
* clear, actionable error at the point of use.
|
|
417
|
-
*
|
|
418
|
-
* @example
|
|
419
|
-
* ```ts
|
|
420
|
-
* const { dateType } = getOasAdapter(ctx.adapter).options
|
|
421
|
-
* ```
|
|
422
|
-
*/
|
|
423
|
-
function getOasAdapter(adapter) {
|
|
424
|
-
if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
|
|
425
|
-
return adapter;
|
|
426
|
-
}
|
|
427
|
-
//#endregion
|
|
428
522
|
//#region ../../internals/shared/src/resolver.ts
|
|
429
523
|
/**
|
|
430
524
|
* Resolves a single operation parameter name with the
|
|
@@ -437,6 +531,29 @@ function operationParamName(node, param) {
|
|
|
437
531
|
return this.name(`${node.operationId} ${param.in} ${param.name}`);
|
|
438
532
|
}
|
|
439
533
|
/**
|
|
534
|
+
* Builds the shared `param` namespace. Spread the result into `createResolver`
|
|
535
|
+
* and override individual methods next to it when a plugin deviates.
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```ts
|
|
539
|
+
* createResolver<PluginTs>({ param: createOperationParamResolver(), ... })
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
function createOperationParamResolver() {
|
|
543
|
+
return {
|
|
544
|
+
name: operationParamName,
|
|
545
|
+
path(node) {
|
|
546
|
+
return this.name(`${node.operationId} Path`);
|
|
547
|
+
},
|
|
548
|
+
query(node) {
|
|
549
|
+
return this.name(`${node.operationId} Query`);
|
|
550
|
+
},
|
|
551
|
+
headers(node) {
|
|
552
|
+
return this.name(`${node.operationId} Headers`);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
440
557
|
* Builds the shared `response` namespace. Spread the result into
|
|
441
558
|
* `createResolver` and add plugin-specific methods (`options`, `error`) next
|
|
442
559
|
* to it.
|
|
@@ -484,7 +601,7 @@ function createCasedFile(caseLast) {
|
|
|
484
601
|
* `resolver.imports` would resolve file paths that are then discarded.
|
|
485
602
|
*/
|
|
486
603
|
function collectRefNames(schema) {
|
|
487
|
-
return ast.
|
|
604
|
+
return ast.collectSync(schema, { schema: (node) => {
|
|
488
605
|
const refNode = ast.narrowSchema(node, "ref");
|
|
489
606
|
if (!refNode?.ref) return null;
|
|
490
607
|
return ast.resolveRefName(refNode);
|
|
@@ -644,12 +761,12 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
|
644
761
|
if (hasCodec(node)) return true;
|
|
645
762
|
if (node.type === "ref") {
|
|
646
763
|
if (!node.ref) return false;
|
|
647
|
-
const refName =
|
|
764
|
+
const refName = extractRefName(node.ref);
|
|
648
765
|
if (refName) {
|
|
649
766
|
if (seen.has(refName)) return false;
|
|
650
767
|
seen.add(refName);
|
|
651
768
|
}
|
|
652
|
-
const resolved =
|
|
769
|
+
const resolved = syncSchemaRef(node);
|
|
653
770
|
if (resolved.type === "ref") return false;
|
|
654
771
|
return containsCodec(resolved, seen);
|
|
655
772
|
}
|
|
@@ -665,7 +782,7 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
|
665
782
|
* them to their input (encode) variant.
|
|
666
783
|
*/
|
|
667
784
|
function collectCodecRefNames(node) {
|
|
668
|
-
return ast.
|
|
785
|
+
return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.resolveRefName(n) ?? void 0 : void 0 });
|
|
669
786
|
}
|
|
670
787
|
/**
|
|
671
788
|
* Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
|
|
@@ -688,7 +805,7 @@ function isObjectSchemaNode(node, cyclicSchemas) {
|
|
|
688
805
|
if (node.type === "ref") {
|
|
689
806
|
const refName = ast.resolveRefName(node);
|
|
690
807
|
if (refName && cyclicSchemas?.has(refName)) return false;
|
|
691
|
-
const resolved =
|
|
808
|
+
const resolved = syncSchemaRef(node);
|
|
692
809
|
return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
|
|
693
810
|
}
|
|
694
811
|
if (node.type === "union") {
|
|
@@ -764,6 +881,20 @@ function buildEnum(values) {
|
|
|
764
881
|
return `z.union([${literals.join(", ")}])`;
|
|
765
882
|
}
|
|
766
883
|
/**
|
|
884
|
+
* Digit pattern for a `type: 'string'` schema that carries an integer `format`, the way ProtoJSON
|
|
885
|
+
* encodes 64-bit integers. Returns `undefined` for every other format, and a `pattern` from the
|
|
886
|
+
* spec takes precedence over this fallback.
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```ts
|
|
890
|
+
* integerFormatPattern('uint64') // '^\\d+$'
|
|
891
|
+
* ```
|
|
892
|
+
*/
|
|
893
|
+
function integerFormatPattern(format) {
|
|
894
|
+
if (format === "int32" || format === "int64") return "^-?\\d+$";
|
|
895
|
+
if (format === "uint64") return "^\\d+$";
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
767
898
|
* Map a `regexType` to the `func` argument of `toRegExpString`: `'constructor'` emits
|
|
768
899
|
* `new RegExp(...)`, while `'literal'` (the default) emits a regex literal.
|
|
769
900
|
*/
|
|
@@ -888,6 +1019,22 @@ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaul
|
|
|
888
1019
|
const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
|
|
889
1020
|
return literal !== null ? `z._default(${withModifier}, ${literal})` : withModifier;
|
|
890
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Builds an `object` schema node grouping the given parameter nodes.
|
|
1024
|
+
* The `primitive: 'object'` marker ensures the Zod printer emits `z.object(…)` rather than a record.
|
|
1025
|
+
*/
|
|
1026
|
+
function buildGroupedParamsSchema({ params, optional }) {
|
|
1027
|
+
return ast.factory.createSchema({
|
|
1028
|
+
type: "object",
|
|
1029
|
+
optional,
|
|
1030
|
+
primitive: "object",
|
|
1031
|
+
properties: params.map((param) => ast.factory.createProperty({
|
|
1032
|
+
name: param.name,
|
|
1033
|
+
required: param.required,
|
|
1034
|
+
schema: param.schema
|
|
1035
|
+
}))
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
891
1038
|
//#endregion
|
|
892
1039
|
//#region src/printers/printerZod.ts
|
|
893
1040
|
function strictOneOfMember$1(member, node, cyclicSchemas) {
|
|
@@ -896,7 +1043,7 @@ function strictOneOfMember$1(member, node, cyclicSchemas) {
|
|
|
896
1043
|
if (member.startsWith("z.lazy(")) return member;
|
|
897
1044
|
const refName = ast.resolveRefName(node);
|
|
898
1045
|
if (refName && cyclicSchemas?.has(refName)) return member;
|
|
899
|
-
const schema =
|
|
1046
|
+
const schema = syncSchemaRef(node);
|
|
900
1047
|
if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
|
|
901
1048
|
if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
|
|
902
1049
|
}
|
|
@@ -921,7 +1068,7 @@ function getMemberConstraint({ member, regexType }) {
|
|
|
921
1068
|
function buildZodObjectShape(ctx, node) {
|
|
922
1069
|
const objectNode = ast.narrowSchema(node, "object");
|
|
923
1070
|
if (!objectNode) return "{}";
|
|
924
|
-
const isCyclic = (schema) => ctx.options.cyclicSchemas != null &&
|
|
1071
|
+
const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
|
|
925
1072
|
return buildObject(mapSchemaProperties(objectNode, (schema) => {
|
|
926
1073
|
const hasSelfRef = isCyclic(schema);
|
|
927
1074
|
const savedCyclicSchemas = ctx.options.cyclicSchemas;
|
|
@@ -931,7 +1078,7 @@ function buildZodObjectShape(ctx, node) {
|
|
|
931
1078
|
return baseOutput;
|
|
932
1079
|
}).map(({ name: propName, property, output: baseOutput }) => {
|
|
933
1080
|
const { schema } = property;
|
|
934
|
-
const meta =
|
|
1081
|
+
const meta = syncSchemaRef(schema);
|
|
935
1082
|
const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
|
|
936
1083
|
const value = applyModifiers({
|
|
937
1084
|
value: baseOutput,
|
|
@@ -974,8 +1121,11 @@ const printerZod = ast.createPrinter((options) => {
|
|
|
974
1121
|
boolean: () => "z.boolean()",
|
|
975
1122
|
null: () => "z.null()",
|
|
976
1123
|
string(node) {
|
|
977
|
-
|
|
1124
|
+
const base = shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()";
|
|
1125
|
+
const pattern = node.pattern ?? integerFormatPattern(node.format);
|
|
1126
|
+
return `${base}${lengthConstraints({
|
|
978
1127
|
...node,
|
|
1128
|
+
pattern,
|
|
979
1129
|
regexType: this.options.regexType
|
|
980
1130
|
})}`;
|
|
981
1131
|
},
|
|
@@ -1116,7 +1266,7 @@ const printerZod = ast.createPrinter((options) => {
|
|
|
1116
1266
|
const { keysToOmit } = this.options;
|
|
1117
1267
|
const transformed = this.transform(node);
|
|
1118
1268
|
if (!transformed) return null;
|
|
1119
|
-
const meta =
|
|
1269
|
+
const meta = syncSchemaRef(node);
|
|
1120
1270
|
return applyModifiers({
|
|
1121
1271
|
value: (() => {
|
|
1122
1272
|
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
@@ -1161,7 +1311,7 @@ function getMemberConstraintMini({ member, regexType }) {
|
|
|
1161
1311
|
function buildZodMiniObjectShape(ctx, node) {
|
|
1162
1312
|
const objectNode = ast.narrowSchema(node, "object");
|
|
1163
1313
|
if (!objectNode) return "{}";
|
|
1164
|
-
const isCyclic = (schema) => ctx.options.cyclicSchemas != null &&
|
|
1314
|
+
const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
|
|
1165
1315
|
return buildObject(mapSchemaProperties(objectNode, (schema) => {
|
|
1166
1316
|
const hasSelfRef = isCyclic(schema);
|
|
1167
1317
|
const savedCyclicSchemas = ctx.options.cyclicSchemas;
|
|
@@ -1171,7 +1321,7 @@ function buildZodMiniObjectShape(ctx, node) {
|
|
|
1171
1321
|
return baseOutput;
|
|
1172
1322
|
}).map(({ name: propName, property, output: baseOutput }) => {
|
|
1173
1323
|
const { schema } = property;
|
|
1174
|
-
const meta =
|
|
1324
|
+
const meta = syncSchemaRef(schema);
|
|
1175
1325
|
const value = applyMiniModifiers({
|
|
1176
1326
|
value: baseOutput,
|
|
1177
1327
|
schema,
|
|
@@ -1211,8 +1361,10 @@ const printerZodMini = ast.createPrinter((options) => {
|
|
|
1211
1361
|
boolean: () => "z.boolean()",
|
|
1212
1362
|
null: () => "z.null()",
|
|
1213
1363
|
string(node) {
|
|
1364
|
+
const pattern = node.pattern ?? integerFormatPattern(node.format);
|
|
1214
1365
|
return `z.string()${lengthChecksMini({
|
|
1215
1366
|
...node,
|
|
1367
|
+
pattern,
|
|
1216
1368
|
regexType: this.options.regexType
|
|
1217
1369
|
})}`;
|
|
1218
1370
|
},
|
|
@@ -1342,7 +1494,7 @@ const printerZodMini = ast.createPrinter((options) => {
|
|
|
1342
1494
|
const { keysToOmit } = this.options;
|
|
1343
1495
|
const transformed = this.transform(node);
|
|
1344
1496
|
if (!transformed) return null;
|
|
1345
|
-
const meta =
|
|
1497
|
+
const meta = syncSchemaRef(node);
|
|
1346
1498
|
return applyMiniModifiers({
|
|
1347
1499
|
value: (() => {
|
|
1348
1500
|
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
@@ -1693,6 +1845,30 @@ const zodGenerator = defineGenerator({
|
|
|
1693
1845
|
description: node.requestBody.description ?? schema.description
|
|
1694
1846
|
}), "input");
|
|
1695
1847
|
})();
|
|
1848
|
+
const { path, query, header } = getOperationParameters(node);
|
|
1849
|
+
const paramGroupSchemas = inferred ? [
|
|
1850
|
+
{
|
|
1851
|
+
kind: "path",
|
|
1852
|
+
params: path
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
kind: "query",
|
|
1856
|
+
params: query
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
kind: "headers",
|
|
1860
|
+
params: header
|
|
1861
|
+
}
|
|
1862
|
+
].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
|
|
1863
|
+
schema: buildGroupedParamsSchema({ params }),
|
|
1864
|
+
name: resolver.param[kind](node, params[0]),
|
|
1865
|
+
direction: "input"
|
|
1866
|
+
})) : [];
|
|
1867
|
+
const optionsSchema = inferred ? renderSchemaEntry({
|
|
1868
|
+
schema: buildOptionsSchema(node, resolver),
|
|
1869
|
+
name: resolver.name(`${node.operationId} Options`),
|
|
1870
|
+
direction: "input"
|
|
1871
|
+
}) : null;
|
|
1696
1872
|
return /* @__PURE__ */ jsxs(File, {
|
|
1697
1873
|
baseName: meta.file.baseName,
|
|
1698
1874
|
path: meta.file.path,
|
|
@@ -1723,7 +1899,9 @@ const zodGenerator = defineGenerator({
|
|
|
1723
1899
|
responseSchemas,
|
|
1724
1900
|
responseUnionSchema,
|
|
1725
1901
|
errorUnionSchema,
|
|
1726
|
-
requestSchema
|
|
1902
|
+
requestSchema,
|
|
1903
|
+
paramGroupSchemas,
|
|
1904
|
+
optionsSchema
|
|
1727
1905
|
]
|
|
1728
1906
|
});
|
|
1729
1907
|
}
|
|
@@ -1765,22 +1943,14 @@ const resolverZod = createResolver({
|
|
|
1765
1943
|
return this.schema.typeName(`${name} input`);
|
|
1766
1944
|
}
|
|
1767
1945
|
},
|
|
1768
|
-
param:
|
|
1769
|
-
name: operationParamName,
|
|
1770
|
-
path(node, param) {
|
|
1771
|
-
return this.param.name(node, param);
|
|
1772
|
-
},
|
|
1773
|
-
query(node, param) {
|
|
1774
|
-
return this.param.name(node, param);
|
|
1775
|
-
},
|
|
1776
|
-
headers(node, param) {
|
|
1777
|
-
return this.param.name(node, param);
|
|
1778
|
-
}
|
|
1779
|
-
},
|
|
1946
|
+
param: createOperationParamResolver(),
|
|
1780
1947
|
response: {
|
|
1781
1948
|
...createOperationResponseResolver(),
|
|
1782
1949
|
error(node) {
|
|
1783
1950
|
return this.name(`${node.operationId} Error`);
|
|
1951
|
+
},
|
|
1952
|
+
options(node) {
|
|
1953
|
+
return this.schema.type(this.name(`${node.operationId} Options`));
|
|
1784
1954
|
}
|
|
1785
1955
|
}
|
|
1786
1956
|
});
|