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

Bugfix: Enum.extract/exclude should not remove error mapping #3240

Merged
merged 1 commit into from
Feb 15, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`tRPC`](https://github.com/trpc/trpc): Build end-to-end typesafe APIs without GraphQL.
- [`@anatine/zod-nestjs`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-nestjs): Helper methods for using Zod in a NestJS project.
- [`zod-endpoints`](https://github.com/flock-community/zod-endpoints): Contract-first strictly typed endpoints with Zod. OpenAPI compatible.
- [`zhttp`](https://github.com/evertdespiegeleer/zhttp): An OpenAPI compatible, strictly typed http library with Zod input and response validation.
- [`zhttp`](https://github.com/evertdespiegeleer/zhttp): An OpenAPI compatible, strictly typed http library with Zod input and response validation.
- [`domain-functions`](https://github.com/SeasonedSoftware/domain-functions/): Decouple your business logic from your framework using composable functions. With first-class type inference from end to end powered by Zod schemas.
- [`@zodios/core`](https://github.com/ecyrbe/zodios): A typescript API client with runtime and compile time validation backed by axios and zod.
- [`express-zod-api`](https://github.com/RobinTail/express-zod-api): Build Express-based APIs with I/O schema validation and custom middlewares.
Expand Down Expand Up @@ -584,8 +584,10 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`freerstore`](https://github.com/JacobWeisenburger/freerstore): Firestore cost optimizer.
- [`slonik`](https://github.com/gajus/slonik/tree/gajus/add-zod-validation-backwards-compatible#runtime-validation-and-static-type-inference): Node.js Postgres client with strong Zod integration.
- [`soly`](https://github.com/mdbetancourt/soly): Create CLI applications with zod.
- [`pastel`](https://github.com/vadimdemedes/pastel): Create CLI applications with react, zod, and ink.
- [`zod-xlsx`](https://github.com/sidwebworks/zod-xlsx): A xlsx based resource validator using Zod schemas.
- [`znv`](https://github.com/lostfictions/znv): Type-safe environment parsing and validation for Node.js with Zod schemas.
- [`zod-config`](https://github.com/alexmarqs/zod-config): Load configurations across multiple sources with flexible adapters, ensuring type safety with Zod.

#### Utilities for Zod

Expand Down Expand Up @@ -963,7 +965,7 @@ You can customize certain error messages when creating a nan schema.
```ts
const isNaN = z.nan({
required_error: "isNaN is required",
invalid_type_error: "isNaN must be not a number",
invalid_type_error: "isNaN must be 'not a number'",
});
```

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 @@ -58,3 +58,20 @@ test("extract/exclude", () => {
util.assertEqual<typeof EmptyFoodEnum, z.ZodEnum<[]>>(true);
util.assertEqual<z.infer<typeof EmptyFoodEnum>, never>(true);
});

test('error map in extract/exclude', () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods, { errorMap: () => ({ message: "This is not food!" }) });
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
const foodsError = FoodEnum.safeParse("Cucumbers");
const italianError = ItalianEnum.safeParse("Tacos");
if (!foodsError.success && !italianError.success) {
expect(foodsError.error.issues[0].message).toEqual(italianError.error.issues[0].message);
}

const UnhealthyEnum = FoodEnum.exclude(["Salad"], { errorMap: () => ({ message: "This is not healthy food!" }) });
const unhealthyError = UnhealthyEnum.safeParse("Salad");
if (!unhealthyError.success) {
expect(unhealthyError.error.issues[0].message).toEqual("This is not healthy food!");
}
});
11 changes: 7 additions & 4 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4057,21 +4057,24 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
}

extract<ToExtract extends readonly [T[number], ...T[number][]]>(
values: ToExtract
values: ToExtract,
newDef: RawCreateParams = this._def
): ZodEnum<Writeable<ToExtract>> {
return ZodEnum.create(values) as any;
return ZodEnum.create(values, newDef) as any;
}

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

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 @@ -57,3 +57,20 @@ test("extract/exclude", () => {
util.assertEqual<typeof EmptyFoodEnum, z.ZodEnum<[]>>(true);
util.assertEqual<z.infer<typeof EmptyFoodEnum>, never>(true);
});

test('error map in extract/exclude', () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods, { errorMap: () => ({ message: "This is not food!" }) });
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
const foodsError = FoodEnum.safeParse("Cucumbers");
const italianError = ItalianEnum.safeParse("Tacos");
if (!foodsError.success && !italianError.success) {
expect(foodsError.error.issues[0].message).toEqual(italianError.error.issues[0].message);
}

const UnhealthyEnum = FoodEnum.exclude(["Salad"], { errorMap: () => ({ message: "This is not healthy food!" }) });
const unhealthyError = UnhealthyEnum.safeParse("Salad");
if (!unhealthyError.success) {
expect(unhealthyError.error.issues[0].message).toEqual("This is not healthy food!");
}
});
11 changes: 7 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4057,21 +4057,24 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
}

extract<ToExtract extends readonly [T[number], ...T[number][]]>(
values: ToExtract
values: ToExtract,
newDef: RawCreateParams = this._def
): ZodEnum<Writeable<ToExtract>> {
return ZodEnum.create(values) as any;
return ZodEnum.create(values, newDef) as any;
}

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

Expand Down
Loading