Skip to content

Commit

Permalink
feat: Add extract/exclude methods to ZodEnum (#1652)
Browse files Browse the repository at this point in the history
* Add extract/exclude to ZodEnum

* Add specs for extract/exclude

* Fix types that were failing build

* Add test to extract exclude

---------

Co-authored-by: santosmarco <eu@marco.rio.br>
  • Loading branch information
santosmarco-caribou and santosmarco committed Feb 8, 2023
1 parent 1e74925 commit 37d83e8
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 0 deletions.
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 @@ -3547,6 +3547,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 @@ -3621,6 +3629,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 @@ -3547,6 +3547,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 @@ -3621,6 +3629,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

0 comments on commit 37d83e8

Please sign in to comment.