@kubb/plugin-zod 5.0.0-beta.75 → 5.0.0-beta.76

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.d.ts CHANGED
@@ -282,6 +282,14 @@ type ResolverZod = Resolver & OperationParamsResolver & {
282
282
  * `resolver.resolveResponseName(node) // → 'listPetsResponseSchema'`
283
283
  */
284
284
  resolveResponseName(this: ResolverZod, node: ast.OperationNode): string;
285
+ /**
286
+ * Resolves the name for the union of an operation's error (non-2xx) responses. Generated clients
287
+ * validate the error body against this on the non-throw path.
288
+ *
289
+ * @example Error union names
290
+ * `resolver.resolveErrorName(node) // → 'listPetsErrorSchema'`
291
+ */
292
+ resolveErrorName(this: ResolverZod, node: ast.OperationNode): string;
285
293
  /**
286
294
  * Resolves the name for an operation's grouped path parameters type.
287
295
  *
package/dist/index.js CHANGED
@@ -432,6 +432,28 @@ function regexFunc(regexType) {
432
432
  return regexType === "constructor" ? "RegExp" : null;
433
433
  }
434
434
  /**
435
+ * Builds a Zod record key schema that enforces the `patternProperties` regexes a plain
436
+ * `.catchall()` would drop. Several patterns combine into one alternation (`(^a)|(^b)`).
437
+ */
438
+ function patternKeySchema({ patterns, regexType }) {
439
+ return `z.string().regex(${patternKeySource({
440
+ patterns,
441
+ regexType
442
+ })})`;
443
+ }
444
+ /**
445
+ * `zod/mini` variant of {@link patternKeySchema}, wrapping the regex in `.check(z.regex(...))`.
446
+ */
447
+ function patternKeySchemaMini({ patterns, regexType }) {
448
+ return `z.string().check(z.regex(${patternKeySource({
449
+ patterns,
450
+ regexType
451
+ })}))`;
452
+ }
453
+ function patternKeySource({ patterns, regexType }) {
454
+ return toRegExpString(patterns.length === 1 ? patterns[0] : patterns.map((pattern) => `(${pattern})`).join("|"), regexFunc(regexType));
455
+ }
456
+ /**
435
457
  * Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
436
458
  * using the standard chainable Zod v4 API.
437
459
  */
@@ -621,7 +643,7 @@ const printerZod = ast.createPrinter((options) => {
621
643
  },
622
644
  object(node) {
623
645
  const isCyclic = (schema) => this.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
624
- const objectBase = `z.object(${buildObject(mapSchemaProperties(node, (schema) => {
646
+ const entries = mapSchemaProperties(node, (schema) => {
625
647
  const hasSelfRef = isCyclic(schema);
626
648
  const savedCyclicSchemas = this.options.cyclicSchemas;
627
649
  if (hasSelfRef) this.options.cyclicSchemas = void 0;
@@ -649,14 +671,29 @@ const printerZod = ast.createPrinter((options) => {
649
671
  name: propName,
650
672
  body: value
651
673
  }) : `${objectKey(propName)}: ${value}`;
652
- }))})`;
674
+ });
675
+ const objectBase = `z.object(${buildObject(entries)})`;
653
676
  return (() => {
677
+ const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
654
678
  if (node.additionalProperties && node.additionalProperties !== true) {
655
679
  const catchallType = this.transform(node.additionalProperties);
656
680
  return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
657
681
  }
658
682
  if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
659
- if (node.additionalProperties === false) return `${objectBase}.strict()`;
683
+ if (node.additionalProperties === false && patterns.length === 0) return `${objectBase}.strict()`;
684
+ if (patterns.length > 0) {
685
+ const values = patterns.map(([, valueSchema]) => {
686
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
687
+ return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
688
+ });
689
+ const distinct = [...new Set(values)];
690
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
691
+ if (entries.length > 0) return `${objectBase}.catchall(${value})`;
692
+ return `z.record(${patternKeySchema({
693
+ patterns: patterns.map(([pattern]) => pattern),
694
+ regexType: this.options.regexType
695
+ })}, ${value})`;
696
+ }
660
697
  return objectBase;
661
698
  })();
662
699
  },
@@ -824,7 +861,7 @@ const printerZodMini = ast.createPrinter((options) => {
824
861
  },
825
862
  object(node) {
826
863
  const isCyclic = (schema) => this.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
827
- return `z.object(${buildObject(mapSchemaProperties(node, (schema) => {
864
+ const entries = mapSchemaProperties(node, (schema) => {
828
865
  const hasSelfRef = isCyclic(schema);
829
866
  const savedCyclicSchemas = this.options.cyclicSchemas;
830
867
  if (hasSelfRef) this.options.cyclicSchemas = void 0;
@@ -848,7 +885,29 @@ const printerZodMini = ast.createPrinter((options) => {
848
885
  name: propName,
849
886
  body: value
850
887
  }) : `${objectKey(propName)}: ${value}`;
851
- }))})`;
888
+ });
889
+ const objectBase = `z.object(${buildObject(entries)})`;
890
+ const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
891
+ if (node.additionalProperties && node.additionalProperties !== true) {
892
+ const catchallType = this.transform(node.additionalProperties);
893
+ return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
894
+ }
895
+ if (node.additionalProperties === true) return `z.catchall(${objectBase}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
896
+ if (node.additionalProperties === false && patterns.length === 0) return objectBase.replace(/^z\.object\(/, "z.strictObject(");
897
+ if (patterns.length > 0) {
898
+ const values = patterns.map(([, valueSchema]) => {
899
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
900
+ return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
901
+ });
902
+ const distinct = [...new Set(values)];
903
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
904
+ if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
905
+ return `z.record(${patternKeySchemaMini({
906
+ patterns: patterns.map(([pattern]) => pattern),
907
+ regexType: this.options.regexType
908
+ })}, ${value})`;
909
+ }
910
+ return objectBase;
852
911
  },
853
912
  array(node) {
854
913
  const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini({
@@ -1227,6 +1286,25 @@ const zodGenerator = defineGenerator({
1227
1286
  name: responseUnionName
1228
1287
  });
1229
1288
  })() : null;
1289
+ const errorResponsesWithSchema = responsesWithSchema.filter((res) => !isSuccessStatusCode(res.statusCode));
1290
+ const errorUnionSchema = errorResponsesWithSchema.length > 0 ? (() => {
1291
+ const errorUnionName = resolver.resolveErrorName(node);
1292
+ if (new Set(errorResponsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
1293
+ name: resolver.resolveSchemaName(schemaName),
1294
+ path: ""
1295
+ })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(errorUnionName)) return null;
1296
+ const members = errorResponsesWithSchema.map((res) => ast.factory.createSchema({
1297
+ type: "ref",
1298
+ name: resolver.resolveResponseStatusName(node, res.statusCode)
1299
+ }));
1300
+ return renderSchemaEntry({
1301
+ schema: members.length === 1 ? members[0] : ast.factory.createSchema({
1302
+ type: "union",
1303
+ members
1304
+ }),
1305
+ name: errorUnionName
1306
+ });
1307
+ })() : null;
1230
1308
  const requestBodyContent = node.requestBody?.content ?? [];
1231
1309
  const requestSchema = (() => {
1232
1310
  if (requestBodyContent.length === 0) return null;
@@ -1277,6 +1355,7 @@ const zodGenerator = defineGenerator({
1277
1355
  paramSchemas,
1278
1356
  responseSchemas,
1279
1357
  responseUnionSchema,
1358
+ errorUnionSchema,
1280
1359
  requestSchema
1281
1360
  ]
1282
1361
  });
@@ -1340,6 +1419,9 @@ const resolverZod = defineResolver(() => {
1340
1419
  resolveResponseName(node) {
1341
1420
  return this.resolveSchemaName(`${node.operationId} Response`);
1342
1421
  },
1422
+ resolveErrorName(node) {
1423
+ return this.resolveSchemaName(`${node.operationId} Error`);
1424
+ },
1343
1425
  resolvePathParamsName(node, param) {
1344
1426
  return this.resolveParamName(node, param);
1345
1427
  },