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

added label in zod type Close #1767 #1902

Closed
wants to merge 2 commits into from
Closed
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
140 changes: 85 additions & 55 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
- [Deno](#from-denolandx-deno)
- [Basic usage](#basic-usage)
- [Primitives](#primitives)
- [Coercion for primitives](#coercion-for-primitives)
- [Literals](#literals)
- [Strings](#strings)
- [Numbers](#numbers)
Expand Down Expand Up @@ -361,6 +362,7 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`zod-formik-adapter`](https://github.com/robertLichtnow/zod-formik-adapter): A community-maintained Formik adapter for Zod.
- [`react-zorm`](https://github.com/esamattis/react-zorm): Standalone `<form>` generation and validation for React using Zod.
- [`zodix`](https://github.com/rileytomasek/zodix): Zod utilities for FormData and URLSearchParams in Remix loaders and actions.
- [`remix-params-helper`](https://github.com/kiliman/remix-params-helper): Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps.
- [`formik-validator-zod`](https://github.com/glazy/formik-validator-zod): Formik-compliant validator library that simplifies using Zod with Formik.
- [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): Useful for translating Zod error messages.
- [`@modular-forms/solid`](https://github.com/fabian-hiller/modular-forms): Modular form library for SolidJS that supports Zod for validation.
Expand Down Expand Up @@ -391,7 +393,7 @@ There are a growing number of tools that are built atop or support Zod natively!

#### Mocking

- [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock): Generate mock data from a Zod schema. Powered by [faker.js](https://github.com/Marak/Faker.js).
- [`@anatine/zod-mock`](https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock): Generate mock data from a Zod schema. Powered by [faker.js](https://github.com/faker-js/faker).
- [`zod-mocking`](https://github.com/dipasqualew/zod-mocking): Generate mock data from your Zod schemas.

#### Powered by Zod
Expand Down Expand Up @@ -507,8 +509,53 @@ z.unknown();
z.never();
```

## Coercion for primitives

Zod now provides a more convenient way to coerce primitive values.

```ts
const schema = z.coerce.string();
schema.parse("tuna"); // => "tuna"
schema.parse(12); // => "12"
schema.parse(true); // => "true"
```

During the parsing step, the input is passed through the `String()` function, which is a JavaScript built-in for coercing data into strings. Note that the returned schema is a `ZodString` instance so you can use all string methods.

```ts
z.coerce.string().email().min(5);
```

All primitive types support coercion.

```ts
z.coerce.string(); // String(input)
z.coerce.number(); // Number(input)
z.coerce.boolean(); // Boolean(input)
z.coerce.bigint(); // BigInt(input)
z.coerce.date(); // new Date(input)
```

**Boolean coercion**

Zod's boolean coercion is very simple! It passes the value into the `Boolean(value)` function, that's it. Any truthy value will resolve to `true`, any falsy value will resolve to `false`.

```ts
z.coerce.boolean().parse("tuna"); // => true
z.coerce.boolean().parse("true"); // => true
z.coerce.boolean().parse("false"); // => true
z.coerce.boolean().parse(1); // => true
z.coerce.boolean().parse([]); // => true

z.coerce.boolean().parse(0); // => false
z.coerce.boolean().parse(undefined); // => false
z.coerce.boolean().parse(null); // => false
```

## Literals

Literals are zod's equivilant to [TypeScript's Literal Types](https://www.typescriptlang.org/docs/handbook/literal-types.html) which alow only the exact given type and value.

```ts
const tuna = z.literal("tuna");
const twelve = z.literal(12);
Expand Down Expand Up @@ -568,49 +615,6 @@ z.string().endsWith(".com", { message: "Only .com domains allowed" });
z.string().datetime({ message: "Invalid datetime string! Must be UTC." });
```

## Coercion for primitives

Zod now provides a more convenient way to coerce primitive values.

```ts
const schema = z.coerce.string();
schema.parse("tuna"); // => "tuna"
schema.parse(12); // => "12"
schema.parse(true); // => "true"
```

During the parsing step, the input is passed through the `String()` function, which is a JavaScript built-in for coercing data into strings. Note that the returned schema is a `ZodString` instance so you can use all string methods.

```ts
z.coerce.string().email().min(5);
```

All primitive types support coercion.

```ts
z.coerce.string(); // String(input)
z.coerce.number(); // Number(input)
z.coerce.boolean(); // Boolean(input)
z.coerce.bigint(); // BigInt(input)
z.coerce.date(); // new Date(input)
```

**Boolean coercion**

Zod's boolean coercion is very simple! It passes the value into the `Boolean(value)` function, that's it. Any truthy value will resolve to `true`, any falsy value will resolve to `false`.

```ts
z.coerce.boolean().parse("tuna"); // => true
z.coerce.boolean().parse("true"); // => true
z.coerce.boolean().parse("false"); // => true
z.coerce.boolean().parse(1); // => true
z.coerce.boolean().parse([]); // => true

z.coerce.boolean().parse(0); // => false
z.coerce.boolean().parse(undefined); // => false
z.coerce.boolean().parse(null); // => false
```

### Datetime validation

The `z.string().datetime()` method defaults to UTC validation: no timezone offsets with arbitrary sub-second decimal precision.
Expand Down Expand Up @@ -729,21 +733,28 @@ z.date().min(new Date("1900-01-01"), { message: "Too old" });
z.date().max(new Date(), { message: "Too young!" });
```

**Supporting date strings**
**Coercion to Date**

To write a schema that accepts either a `Date` or a date string, use [`z.preprocess`](#preprocess).
Since [zod 3.20](https://github.com/colinhacks/zod/releases/tag/v3.20), use [`z.coerce.date()`](#coercion-for-primitives) to pass the input through `new Date(input)`.

```ts
const dateSchema = z.preprocess((arg) => {
if (typeof arg == "string" || arg instanceof Date) return new Date(arg);
}, z.date());
type DateSchema = z.infer<typeof dateSchema>;
const dateSchema = z.coerce.date()
type DateSchema = z.infer<typeof dateSchema>
// type DateSchema = Date

dateSchema.safeParse(new Date("1/12/22")); // success: true
dateSchema.safeParse("2022-01-12T00:00:00.000Z"); // success: true
/* valid dates */
console.log( dateSchema.safeParse( '2023-01-10T00:00:00.000Z' ).success ) // true
console.log( dateSchema.safeParse( '2023-01-10' ).success ) // true
console.log( dateSchema.safeParse( '1/10/23' ).success ) // true
console.log( dateSchema.safeParse( new Date( '1/10/23' ) ).success ) // true

/* invalid dates */
console.log( dateSchema.safeParse( '2023-13-10' ).success ) // false
console.log( dateSchema.safeParse( '0000-00-00' ).success ) // false
```

For older zod versions, use [`z.preprocess`](#preprocess) like [described in this thread](https://github.com/colinhacks/zod/discussions/879#discussioncomment-2036276).

## Zod enums

```ts
Expand Down Expand Up @@ -786,7 +797,7 @@ FishEnum.enum;
You can also retrieve the list of options as a tuple with the `.options` property:

```ts
FishEnum.options; // ["Salmon", "Tuna", "Trout"]);
FishEnum.options; // ["Salmon", "Tuna", "Trout"];
```

## Native enums
Expand Down Expand Up @@ -1270,12 +1281,31 @@ stringOrNumber.parse(14); // passes

Zod will test the input against each of the "options" in order and return the first value that validates successfully.

For convenience, you can also use the `.or` method:
For convenience, you can also use the [`.or` method](#or):

```ts
const stringOrNumber = z.string().or(z.number());
```

**Optional string validation:**

To validate an optional form input, you can union the desired string validation with an empty string [literal](#literals).

This example validates an input that is optional but needs to contain a [valid URL](#strings):

```ts
const optionalUrl = z.union( [
z.string().url().nullish(),
z.literal( '' ),
] )

console.log( optionalUrl.safeParse( undefined ).success ) // true
console.log( optionalUrl.safeParse( null ).success ) // true
console.log( optionalUrl.safeParse( '' ).success ) // true
console.log( optionalUrl.safeParse( 'https://zod.dev' ).success ) // true
console.log( optionalUrl.safeParse( 'not a valid url' ).success ) // false
```

## Discriminated unions

A discriminated union is a union of object schemas that all share a particular key.
Expand Down Expand Up @@ -2142,7 +2172,7 @@ z.promise(z.string());

### `.or`

A convenience method for union types.
A convenience method for [union types](#unions).

```ts
const stringOrNumber = z.string().or(z.number()); // string | number
Expand Down
1 change: 1 addition & 0 deletions deno/lib/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type ZodIssueCode = keyof typeof ZodIssueCode;
export type ZodIssueBase = {
path: (string | number)[];
message?: string;
label?: string;
};

export interface ZodInvalidTypeIssue extends ZodIssueBase {
Expand Down
54 changes: 43 additions & 11 deletions deno/lib/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,82 +3,112 @@ import { ZodErrorMap, ZodIssueCode } from "../ZodError.ts";

const errorMap: ZodErrorMap = (issue, _ctx) => {
let message: string;
// label is available here
// issue.label
switch (issue.code) {
case ZodIssueCode.invalid_type:
if (issue.received === ZodParsedType.undefined) {
message = "Required";
if (issue.label) message = `${issue.label} is Required`;
} else {
message = `Expected ${issue.expected}, received ${issue.received}`;
if (issue.label)
message = `${issue.label} is Expected to be ${issue.expected}, received ${issue.received}`;
}
break;
case ZodIssueCode.invalid_literal:
message = `Invalid literal value, expected ${JSON.stringify(
issue.expected,
util.jsonStringifyReplacer
)}`;
if (issue.label)
message = `Invalid value for ${issue.label} is Expected to be ${issue.expected}`;

break;
case ZodIssueCode.unrecognized_keys:
message = `Unrecognized key(s) in object: ${util.joinValues(
issue.keys,
", "
)}`;

if (issue.label) message = `Extra property is passed in ${issue.label}`;
break;
case ZodIssueCode.invalid_union:
message = `Invalid input`;
if (issue.label) message = `Input is invalid for ${issue.label}`;
break;
case ZodIssueCode.invalid_union_discriminator:
message = `Invalid discriminator value. Expected ${util.joinValues(
issue.options
)}`;
if (issue.label)
message = `Invalid discriminator value in ${
issue.label
}. Expected ${util.joinValues(issue.options)}`;
break;
case ZodIssueCode.invalid_enum_value:
message = `Invalid enum value. Expected ${util.joinValues(
issue.options
)}, received '${issue.received}'`;
if (issue.label) message = `Invalid value in ${issue.label}`;
break;
case ZodIssueCode.invalid_arguments:
message = `Invalid function arguments`;
if (issue.label) message = `Invalid Function arguments in ${issue.label}`;
break;
case ZodIssueCode.invalid_return_type:
message = `Invalid function return type`;
if (issue.label)
message = `Invalid Function return type for ${issue.label}`;
break;
case ZodIssueCode.invalid_date:
message = `Invalid date`;
if (issue.label) message = `${issue.label} is a invalid date`;
break;
case ZodIssueCode.invalid_string:
if (typeof issue.validation === "object") {
if ("startsWith" in issue.validation) {
message = `Invalid input: must start with "${issue.validation.startsWith}"`;
if (issue.label)
message = `${issue.label} must start with "${issue.validation.startsWith}"`;
} else if ("endsWith" in issue.validation) {
message = `Invalid input: must end with "${issue.validation.endsWith}"`;
if (issue.label)
message = `${issue.label} must end with "${issue.validation.endsWith}"`;
} else {
util.assertNever(issue.validation);
}
} else if (issue.validation !== "regex") {
message = `Invalid ${issue.validation}`;
if (issue.label) message = `${issue.label} is invalid`;
} else {
message = "Invalid";
}
break;
case ZodIssueCode.too_small:
if (issue.type === "array")
message = `Array must contain ${
message = `${issue.label ?? "Array"} must contain ${
issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`
} ${issue.minimum} element(s)`;
else if (issue.type === "string")
message = `String must contain ${
issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`
} ${issue.minimum} character(s)`;

if (issue.label)
message = `${issue.label} must be ${
issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`
} ${issue.minimum} character(s)`;
else if (issue.type === "number")
message = `Number must be ${
message = `${issue.label ?? "Number"} must be ${
issue.exact
? `exactly equal to `
: issue.inclusive
? `greater than or equal to `
: `greater than `
}${issue.minimum}`;
else if (issue.type === "date")
message = `Date must be ${
message = `${issue.label ?? "Date"} must be ${
issue.exact
? `exactly equal to `
: issue.inclusive
Expand All @@ -89,42 +119,44 @@ const errorMap: ZodErrorMap = (issue, _ctx) => {
break;
case ZodIssueCode.too_big:
if (issue.type === "array")
message = `Array must contain ${
message = `${issue.label ?? "Array"} must contain ${
issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`
} ${issue.maximum} element(s)`;
else if (issue.type === "string")
message = `String must contain ${
message = `${issue.label ?? "String"} must contain ${
issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`
} ${issue.maximum} character(s)`;
else if (issue.type === "number")
message = `Number must be ${
message = `${issue.label ?? "Number"} must be ${
issue.exact
? `exactly`
: issue.inclusive
? `less than or equal to`
: `less than`
} ${issue.maximum}`;
else if (issue.type === "date")
message = `Date must be ${
message = `${issue.label ?? "Date"} must be ${
issue.exact
? `exactly`
: issue.inclusive
? `smaller than or equal to`
: `smaller than`
} ${new Date(issue.maximum)}`;
else message = "Invalid input";
else message = `Invalid input${issue.label ? ` for ${issue.label}` : ""}`;
break;
case ZodIssueCode.custom:
message = `Invalid input`;
message = `Invalid input${issue.label ? ` for ${issue.label}` : ""}`;
break;
case ZodIssueCode.invalid_intersection_types:
message = `Intersection results could not be merged`;
break;
case ZodIssueCode.not_multiple_of:
message = `Number must be a multiple of ${issue.multipleOf}`;
message = `${issue.label ?? "Number"} must be a multiple of ${
issue.multipleOf
}`;
break;
case ZodIssueCode.not_finite:
message = "Number must be finite";
message = `${issue.label ?? "Number"} must be finite`;
break;
default:
message = _ctx.defaultError;
Expand Down
Loading