Skip to content

Commit 640eb2a

Browse files
committed
fix(zod): tighten guards in tryBuildDiscriminatedUnion and buildDiscriminatorExpression
- Add `items?.length` check before `every()` to prevent vacuous-truth on empty `or.items` array (would have emitted invalid `z.enum([])`) - Add `value.length > 0` guard in `buildDiscriminatorExpression` for the same reason — function is exported so the guard is worth keeping locally - Collapse single-element arrays to `z.literal(value[0])` ahead of the array branches (more ergonomic, consistent with scalar path) All three cases are unreachable today (IR collapses single-item `or` and only emits `or` when `valueSchemas.length > 1`), but tightening keeps the invariants local to the functions rather than relying on upstream guarantees.
1 parent 6de0b51 commit 640eb2a

1 file changed

Lines changed: 10 additions & 3 deletions

File tree

packages/openapi-ts/src/plugins/zod/shared/discriminated-union.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export function tryBuildDiscriminatedUnion({
4848
discriminatedValue = discriminatorProp.const;
4949
} else if (
5050
discriminatorProp?.logicalOperator === 'or' &&
51-
discriminatorProp.items?.every((item) => item.const !== undefined)
51+
discriminatorProp.items?.length &&
52+
discriminatorProp.items.every((item) => item.const !== undefined)
5253
) {
5354
// OR-of-consts — the discriminator maps multiple values to one schema.
5455
// Every item must have a const; a future IR change that violates this would
@@ -99,15 +100,21 @@ export function tryBuildDiscriminatedUnion({
99100
* ZodUnion of literals). The shape to emit depends on the value:
100101
*
101102
* "cat" → z.literal("cat")
102-
* ["a", "b"] → z.enum(["a", "b"]) all strings: enum is more ergonomic than union
103+
* ["x"] → z.literal("x") single-element: literal is more ergonomic
104+
* ["a", "b"] → z.enum(["a", "b"]) all strings: enum is more ergonomic than union
103105
* [1, 2] → z.union([z.literal(1), z.literal(2)])
104106
*/
105107
export function buildDiscriminatorExpression(z: Symbol, value: unknown): Chain {
106108
if (!Array.isArray(value)) {
107109
return $(z).attr(identifiers.literal).call($.fromValue(value));
108110
}
109111

110-
if (value.every((v) => typeof v === 'string')) {
112+
// Single-element array: prefer z.literal over a single-item enum/union
113+
if (value.length === 1) {
114+
return $(z).attr(identifiers.literal).call($.fromValue(value[0]));
115+
}
116+
117+
if (value.length > 0 && value.every((v) => typeof v === 'string')) {
111118
return $(z).attr(identifiers.enum).call($.fromValue(value));
112119
}
113120

0 commit comments

Comments
 (0)