Skip to content

Commit

Permalink
add checks @ ZodBigInt. (#1711)
Browse files Browse the repository at this point in the history
* implement `ZodBigInt` checks.

* add bigint default error messages.

* add `bigint` unit tests.

* add `ZodBigInt.gigantic/unsafe` & `ZodBigInt.safe`.

* add `ZodBigInt.isSafe` & `ZodBigInt.isUnsafe` getters.

* rename `not gigantic` issue to `not unsafe`.

* add `bigint` section @ README.md.

* Remove safe/unsafe/gigantic, consolidate issue types

---------

Co-authored-by: Colin McDonnell <colinmcd@alum.mit.edu>
  • Loading branch information
igalklebanov and Colin McDonnell committed Feb 27, 2023
1 parent 16beeb5 commit 75cb9e8
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 16 deletions.
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,7 @@
- [Datetime](#datetime-validation)
- [IP](#ip-address-validation)
- [Numbers](#numbers)
- [BigInts](#bigints)
- [NaNs](#nans)
- [Booleans](#booleans)
- [Dates](#dates)
Expand Down Expand Up @@ -726,6 +727,24 @@ Optionally, you can pass in a second argument to provide a custom error message.
z.number().lte(5, { message: "this👏is👏too👏big" });
```

## BigInts

Zod includes a handful of bigint-specific validations.

```ts
z.bigint().gt(5n);
z.bigint().gte(5n); // alias `.min(5n)`
z.bigint().lt(5n);
z.bigint().lte(5n); // alias `.max(5n)`

z.bigint().positive(); // > 0n
z.bigint().nonnegative(); // >= 0n
z.bigint().negative(); // < 0n
z.bigint().nonpositive(); // <= 0n

z.bigint().multipleOf(5n); // Evenly divisible by 5n.
```

## NaNs

You can customize certain error messages when creating a nan schema.
Expand Down
19 changes: 19 additions & 0 deletions deno/lib/README.md
Expand Up @@ -58,6 +58,7 @@
- [Datetime](#datetime-validation)
- [IP](#ip-address-validation)
- [Numbers](#numbers)
- [BigInts](#bigints)
- [NaNs](#nans)
- [Booleans](#booleans)
- [Dates](#dates)
Expand Down Expand Up @@ -726,6 +727,24 @@ Optionally, you can pass in a second argument to provide a custom error message.
z.number().lte(5, { message: "this👏is👏too👏big" });
```

## BigInts

Zod includes a handful of bigint-specific validations.

```ts
z.bigint().gt(5n);
z.bigint().gte(5n); // alias `.min(5n)`
z.bigint().lt(5n);
z.bigint().lte(5n); // alias `.max(5n)`

z.bigint().positive(); // > 0n
z.bigint().nonnegative(); // >= 0n
z.bigint().negative(); // < 0n
z.bigint().nonpositive(); // <= 0n

z.bigint().multipleOf(5n); // Evenly divisible by 5n.
```

## NaNs

You can customize certain error messages when creating a nan schema.
Expand Down
10 changes: 5 additions & 5 deletions deno/lib/ZodError.ts
Expand Up @@ -108,18 +108,18 @@ export interface ZodInvalidStringIssue extends ZodIssueBase {

export interface ZodTooSmallIssue extends ZodIssueBase {
code: typeof ZodIssueCode.too_small;
minimum: number;
minimum: number | bigint;
inclusive: boolean;
exact?: boolean;
type: "array" | "string" | "number" | "set" | "date";
type: "array" | "string" | "number" | "set" | "date" | "bigint";
}

export interface ZodTooBigIssue extends ZodIssueBase {
code: typeof ZodIssueCode.too_big;
maximum: number;
maximum: number | bigint;
inclusive: boolean;
exact?: boolean;
type: "array" | "string" | "number" | "set" | "date";
type: "array" | "string" | "number" | "set" | "date" | "bigint";
}

export interface ZodInvalidIntersectionTypesIssue extends ZodIssueBase {
Expand All @@ -128,7 +128,7 @@ export interface ZodInvalidIntersectionTypesIssue extends ZodIssueBase {

export interface ZodNotMultipleOfIssue extends ZodIssueBase {
code: typeof ZodIssueCode.not_multiple_of;
multipleOf: number;
multipleOf: number | bigint;
}

export interface ZodNotFiniteIssue extends ZodIssueBase {
Expand Down
58 changes: 58 additions & 0 deletions deno/lib/__tests__/bigint.test.ts
@@ -0,0 +1,58 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
const test = Deno.test;

import * as z from "../index.ts";

const gtFive = z.bigint().gt(BigInt(5));
const gteFive = z.bigint().gte(BigInt(5));
const ltFive = z.bigint().lt(BigInt(5));
const lteFive = z.bigint().lte(BigInt(5));
const positive = z.bigint().positive();
const negative = z.bigint().negative();
const nonnegative = z.bigint().nonnegative();
const nonpositive = z.bigint().nonpositive();
const multipleOfFive = z.bigint().multipleOf(BigInt(5));

test("passing validations", () => {
z.bigint().parse(BigInt(1));
z.bigint().parse(BigInt(0));
z.bigint().parse(BigInt(-1));
gtFive.parse(BigInt(6));
gteFive.parse(BigInt(5));
gteFive.parse(BigInt(6));
ltFive.parse(BigInt(4));
lteFive.parse(BigInt(5));
lteFive.parse(BigInt(4));
positive.parse(BigInt(3));
negative.parse(BigInt(-2));
nonnegative.parse(BigInt(0));
nonnegative.parse(BigInt(7));
nonpositive.parse(BigInt(0));
nonpositive.parse(BigInt(-12));
multipleOfFive.parse(BigInt(15));
});

test("failing validations", () => {
expect(() => gtFive.parse(BigInt(5))).toThrow();
expect(() => gteFive.parse(BigInt(4))).toThrow();
expect(() => ltFive.parse(BigInt(5))).toThrow();
expect(() => lteFive.parse(BigInt(6))).toThrow();
expect(() => positive.parse(BigInt(0))).toThrow();
expect(() => positive.parse(BigInt(-2))).toThrow();
expect(() => negative.parse(BigInt(0))).toThrow();
expect(() => negative.parse(BigInt(3))).toThrow();
expect(() => nonnegative.parse(BigInt(-1))).toThrow();
expect(() => nonpositive.parse(BigInt(1))).toThrow();
expect(() => multipleOfFive.parse(BigInt(13))).toThrow();
});

test("min max getters", () => {
expect(z.bigint().min(BigInt(5)).minValue).toEqual(BigInt(5));
expect(z.bigint().min(BigInt(5)).min(BigInt(10)).minValue).toEqual(
BigInt(10)
);

expect(z.bigint().max(BigInt(5)).maxValue).toEqual(BigInt(5));
expect(z.bigint().max(BigInt(5)).max(BigInt(1)).maxValue).toEqual(BigInt(1));
});
12 changes: 10 additions & 2 deletions deno/lib/locales/en.ts
Expand Up @@ -84,7 +84,7 @@ const errorMap: ZodErrorMap = (issue, _ctx) => {
: issue.inclusive
? `greater than or equal to `
: `greater than `
}${new Date(issue.minimum)}`;
}${new Date(Number(issue.minimum))}`;
else message = "Invalid input";
break;
case ZodIssueCode.too_big:
Expand All @@ -104,14 +104,22 @@ const errorMap: ZodErrorMap = (issue, _ctx) => {
? `less than or equal to`
: `less than`
} ${issue.maximum}`;
else if (issue.type === "bigint")
message = `BigInt 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 ${
issue.exact
? `exactly`
: issue.inclusive
? `smaller than or equal to`
: `smaller than`
} ${new Date(issue.maximum)}`;
} ${new Date(Number(issue.maximum))}`;
else message = "Invalid input";
break;
case ZodIssueCode.custom:
Expand Down
167 changes: 166 additions & 1 deletion deno/lib/types.ts
Expand Up @@ -1276,8 +1276,13 @@ export class ZodNumber extends ZodType<number, ZodNumberDef> {
////////// //////////
/////////////////////////////////////////
/////////////////////////////////////////
export type ZodBigIntCheck =
| { kind: "min"; value: bigint; inclusive: boolean; message?: string }
| { kind: "max"; value: bigint; inclusive: boolean; message?: string }
| { kind: "multipleOf"; value: bigint; message?: string };

export interface ZodBigIntDef extends ZodTypeDef {
checks: ZodBigIntCheck[];
typeName: ZodFirstPartyTypeKind.ZodBigInt;
coerce: boolean;
}
Expand All @@ -1297,18 +1302,178 @@ export class ZodBigInt extends ZodType<bigint, ZodBigIntDef> {
});
return INVALID;
}
return OK(input.data);

let ctx: undefined | ParseContext = undefined;
const status = new ParseStatus();

for (const check of this._def.checks) {
if (check.kind === "min") {
const tooSmall = check.inclusive
? input.data < check.value
: input.data <= check.value;
if (tooSmall) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
type: "bigint",
minimum: check.value,
inclusive: check.inclusive,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "max") {
const tooBig = check.inclusive
? input.data > check.value
: input.data >= check.value;
if (tooBig) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
type: "bigint",
maximum: check.value,
inclusive: check.inclusive,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "multipleOf") {
if (input.data % check.value !== BigInt(0)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.not_multiple_of,
multipleOf: check.value,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
}

return { status: status.value, value: input.data };
}

static create = (
params?: RawCreateParams & { coerce?: boolean }
): ZodBigInt => {
return new ZodBigInt({
checks: [],
typeName: ZodFirstPartyTypeKind.ZodBigInt,
coerce: params?.coerce ?? false,
...processCreateParams(params),
});
};

gte(value: bigint, message?: errorUtil.ErrMessage) {
return this.setLimit("min", value, true, errorUtil.toString(message));
}
min = this.gte;

gt(value: bigint, message?: errorUtil.ErrMessage) {
return this.setLimit("min", value, false, errorUtil.toString(message));
}

lte(value: bigint, message?: errorUtil.ErrMessage) {
return this.setLimit("max", value, true, errorUtil.toString(message));
}
max = this.lte;

lt(value: bigint, message?: errorUtil.ErrMessage) {
return this.setLimit("max", value, false, errorUtil.toString(message));
}

protected setLimit(
kind: "min" | "max",
value: bigint,
inclusive: boolean,
message?: string
) {
return new ZodBigInt({
...this._def,
checks: [
...this._def.checks,
{
kind,
value,
inclusive,
message: errorUtil.toString(message),
},
],
});
}

_addCheck(check: ZodBigIntCheck) {
return new ZodBigInt({
...this._def,
checks: [...this._def.checks, check],
});
}

positive(message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "min",
value: BigInt(0),
inclusive: false,
message: errorUtil.toString(message),
});
}

negative(message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "max",
value: BigInt(0),
inclusive: false,
message: errorUtil.toString(message),
});
}

nonpositive(message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "max",
value: BigInt(0),
inclusive: true,
message: errorUtil.toString(message),
});
}

nonnegative(message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "min",
value: BigInt(0),
inclusive: true,
message: errorUtil.toString(message),
});
}

multipleOf(value: bigint, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "multipleOf",
value,
message: errorUtil.toString(message),
});
}

get minValue() {
let min: bigint | null = null;
for (const ch of this._def.checks) {
if (ch.kind === "min") {
if (min === null || ch.value > min) min = ch.value;
}
}
return min;
}

get maxValue() {
let max: bigint | null = null;
for (const ch of this._def.checks) {
if (ch.kind === "max") {
if (max === null || ch.value < max) max = ch.value;
}
}
return max;
}
}

//////////////////////////////////////////
Expand Down

0 comments on commit 75cb9e8

Please sign in to comment.