Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add extract/exclude methods to ZodEnum #1652

Merged
merged 4 commits into from
Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1261,7 +1261,7 @@ type MyUnion =
| { status: "failed"; error: Error };
```

Such unions can be represented with the `z.discriminatedUnion` method. This enables faster evaluation, because Zod can check the _discriminator key_ (`status` in the example above) ot determine which schema should be used to parse the input. This makes parsing more efficient and lets Zod provide report friendlier errors.
Such unions can be represented with the `z.discriminatedUnion` method. This enables faster evaluation, because Zod can check the _discriminator key_ (`status` in the example above) to determine which schema should be used to parse the input. This makes parsing more efficient and lets Zod report friendlier errors.

With the basic union method the input is tested against each of the provided "options", and in the case of invalidity, issues for all the "options" are shown in the zod error. On the other hand, the discriminated union allows for selecting just one of the "options", testing against it, and showing only the issues related to this "option".

Expand Down
17 changes: 17 additions & 0 deletions deno/lib/__tests__/enum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,20 @@ test("error params", () => {
expect(result.error.issues[0].message).toEqual("REQUIRED");
}
});

test("extract/exclude", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods);
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
const UnhealthyEnum = FoodEnum.exclude(["Salad"]);
const EmptyFoodEnum = FoodEnum.exclude(foods);

util.assertEqual<z.infer<typeof ItalianEnum>, "Pasta" | "Pizza">(true);
util.assertEqual<
z.infer<typeof UnhealthyEnum>,
"Pasta" | "Pizza" | "Tacos" | "Burgers"
>(true);
// @ts-expect-error TS2344
util.assertEqual<typeof EmptyFoodEnum, z.ZodEnum<[]>>(true);
util.assertEqual<z.infer<typeof EmptyFoodEnum>, never>(true);
});
25 changes: 25 additions & 0 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3426,6 +3426,14 @@ export interface ZodEnumDef<T extends EnumValues = EnumValues>

export type Writeable<T> = { -readonly [P in keyof T]: T[P] };

export type FilterEnum<Values, ToExclude> = Values extends []
? []
: Values extends [infer Head, ...infer Rest]
? Head extends ToExclude
? FilterEnum<Rest, ToExclude>
: [Head, ...FilterEnum<Rest, ToExclude>]
: never;

function createZodEnum<U extends string, T extends Readonly<[U, ...U[]]>>(
values: T,
params?: RawCreateParams
Expand Down Expand Up @@ -3500,6 +3508,23 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
return enumValues as any;
}

extract<ToExtract extends readonly [T[number], ...T[number][]]>(
values: ToExtract
) {
return ZodEnum.create(values);
}

exclude<ToExclude extends readonly [T[number], ...T[number][]]>(
values: ToExclude
) {
return ZodEnum.create(
this.options.filter((opt) => !values.includes(opt)) as FilterEnum<
T,
ToExclude[number]
>
);
}

static create = createZodEnum;
}

Expand Down
17 changes: 17 additions & 0 deletions src/__tests__/enum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,20 @@ test("error params", () => {
expect(result.error.issues[0].message).toEqual("REQUIRED");
}
});

test("extract/exclude", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods);
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
const UnhealthyEnum = FoodEnum.exclude(["Salad"]);
const EmptyFoodEnum = FoodEnum.exclude(foods);

util.assertEqual<z.infer<typeof ItalianEnum>, "Pasta" | "Pizza">(true);
util.assertEqual<
z.infer<typeof UnhealthyEnum>,
"Pasta" | "Pizza" | "Tacos" | "Burgers"
>(true);
// @ts-expect-error TS2344
util.assertEqual<typeof EmptyFoodEnum, z.ZodEnum<[]>>(true);
util.assertEqual<z.infer<typeof EmptyFoodEnum>, never>(true);
});
25 changes: 25 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3426,6 +3426,14 @@ export interface ZodEnumDef<T extends EnumValues = EnumValues>

export type Writeable<T> = { -readonly [P in keyof T]: T[P] };

export type FilterEnum<Values, ToExclude> = Values extends []
? []
: Values extends [infer Head, ...infer Rest]
? Head extends ToExclude
? FilterEnum<Rest, ToExclude>
: [Head, ...FilterEnum<Rest, ToExclude>]
: never;

function createZodEnum<U extends string, T extends Readonly<[U, ...U[]]>>(
values: T,
params?: RawCreateParams
Expand Down Expand Up @@ -3500,6 +3508,23 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
return enumValues as any;
}

extract<ToExtract extends readonly [T[number], ...T[number][]]>(
values: ToExtract
) {
return ZodEnum.create(values);
}

exclude<ToExclude extends readonly [T[number], ...T[number][]]>(
values: ToExclude
) {
return ZodEnum.create(
this.options.filter((opt) => !values.includes(opt)) as FilterEnum<
T,
ToExclude[number]
>
);
}

static create = createZodEnum;
}

Expand Down