Skip to content
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
2 changes: 2 additions & 0 deletions deno/lib/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ export interface ZodTooSmallIssue extends ZodIssueBase {
code: typeof ZodIssueCode.too_small;
minimum: number;
inclusive: boolean;
exact: boolean;
type: "array" | "string" | "number" | "set" | "date";
}

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

Expand Down
36 changes: 36 additions & 0 deletions deno/lib/__tests__/validations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ test("array max", async () => {
}
});

test("array length", async () => {
try {
await z.array(z.string()).length(2).parseAsync(["asdf", "asdf", "asdf"]);
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"Array must contain exactly 2 element(s)"
);
}

try {
await z.array(z.string()).length(2).parseAsync(["asdf"]);
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"Array must contain exactly 2 element(s)"
);
}
});

test("string length", async () => {
try {
await z.string().length(4).parseAsync("asd");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"String must contain exactly 4 character(s)"
);
}

try {
await z.string().length(4).parseAsync("asdaa");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"String must contain exactly 4 character(s)"
);
}
});

test("string min", async () => {
try {
await z.string().min(4).parseAsync("asd");
Expand Down
44 changes: 30 additions & 14 deletions deno/lib/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,39 +63,55 @@ const errorMap: ZodErrorMap = (issue, _ctx) => {
case ZodIssueCode.too_small:
if (issue.type === "array")
message = `Array must contain ${
issue.inclusive ? `at least` : `more than`
issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`
} ${issue.minimum} element(s)`;
else if (issue.type === "string")
message = `String must contain ${
issue.inclusive ? `at least` : `over`
issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`
} ${issue.minimum} character(s)`;
else if (issue.type === "number")
message = `Number must be greater than ${
issue.inclusive ? `or equal to ` : ``
message = `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 greater than ${
issue.inclusive ? `or equal to ` : ``
message = `Date must be ${
issue.exact
? `exactly equal to `
: issue.inclusive
? `greater than or equal to `
: `greater than `
}${new Date(issue.minimum)}`;
else message = "Invalid input";
break;
case ZodIssueCode.too_big:
if (issue.type === "array")
message = `Array must contain ${
issue.inclusive ? `at most` : `less than`
issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`
} ${issue.maximum} element(s)`;
else if (issue.type === "string")
message = `String must contain ${
issue.inclusive ? `at most` : `under`
issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`
} ${issue.maximum} character(s)`;
else if (issue.type === "number")
message = `Number must be less than ${
issue.inclusive ? `or equal to ` : ``
}${issue.maximum}`;
message = `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 smaller than ${
issue.inclusive ? `or equal to ` : ``
}${new Date(issue.maximum)}`;
message = `Date must be ${
issue.exact
? `exactly`
: issue.inclusive
? `smaller than or equal to`
: `smaller than`
} ${new Date(issue.maximum)}`;
else message = "Invalid input";
break;
case ZodIssueCode.custom:
Expand Down
69 changes: 67 additions & 2 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ export abstract class ZodType<
type ZodStringCheck =
| { kind: "min"; value: number; message?: string }
| { kind: "max"; value: number; message?: string }
| { kind: "length"; value: number; message?: string }
| { kind: "email"; message?: string }
| { kind: "url"; message?: string }
| { kind: "uuid"; message?: string }
Expand Down Expand Up @@ -572,6 +573,7 @@ export class ZodString extends ZodType<string, ZodStringDef> {
minimum: check.value,
type: "string",
inclusive: true,
exact: false,
message: check.message,
});
status.dirty();
Expand All @@ -584,10 +586,37 @@ export class ZodString extends ZodType<string, ZodStringDef> {
maximum: check.value,
type: "string",
inclusive: true,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "length") {
const tooBig = input.data.length > check.value;
const tooSmall = input.data.length < check.value;
if (tooBig || tooSmall) {
ctx = this._getOrReturnCtx(input, ctx);
if (tooBig) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
maximum: check.value,
type: "string",
inclusive: true,
exact: true,
message: check.message,
});
} else if (tooSmall) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: check.value,
type: "string",
inclusive: true,
exact: true,
message: check.message,
});
}
status.dirty();
}
} else if (check.kind === "email") {
if (!emailRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
Expand Down Expand Up @@ -781,7 +810,11 @@ export class ZodString extends ZodType<string, ZodStringDef> {
}

length(len: number, message?: errorUtil.ErrMessage) {
return this.min(len, message).max(len, message);
return this._addCheck({
kind: "length",
value: len,
...errorUtil.errToObj(message),
});
}

/**
Expand Down Expand Up @@ -910,6 +943,7 @@ export class ZodNumber extends ZodType<number, ZodNumberDef> {
minimum: check.value,
type: "number",
inclusive: check.inclusive,
exact: false,
message: check.message,
});
status.dirty();
Expand All @@ -925,6 +959,7 @@ export class ZodNumber extends ZodType<number, ZodNumberDef> {
maximum: check.value,
type: "number",
inclusive: check.inclusive,
exact: false,
message: check.message,
});
status.dirty();
Expand Down Expand Up @@ -1211,6 +1246,7 @@ export class ZodDate extends ZodType<Date, ZodDateDef> {
code: ZodIssueCode.too_small,
message: check.message,
inclusive: true,
exact: false,
minimum: check.value,
type: "date",
});
Expand All @@ -1223,6 +1259,7 @@ export class ZodDate extends ZodType<Date, ZodDateDef> {
code: ZodIssueCode.too_big,
message: check.message,
inclusive: true,
exact: false,
maximum: check.value,
type: "date",
});
Expand Down Expand Up @@ -1521,6 +1558,7 @@ export interface ZodArrayDef<T extends ZodTypeAny = ZodTypeAny>
extends ZodTypeDef {
type: T;
typeName: ZodFirstPartyTypeKind.ZodArray;
exactLength: { value: number; message?: string } | null;
minLength: { value: number; message?: string } | null;
maxLength: { value: number; message?: string } | null;
}
Expand Down Expand Up @@ -1557,13 +1595,31 @@ export class ZodArray<
return INVALID;
}

if (def.exactLength !== null) {
const tooBig = ctx.data.length > def.exactLength.value;
const tooSmall = ctx.data.length < def.exactLength.value;
if (tooBig || tooSmall) {
addIssueToContext(ctx, {
code: tooBig ? ZodIssueCode.too_big : ZodIssueCode.too_small,
minimum: (tooSmall ? def.exactLength.value : undefined) as number,
maximum: (tooBig ? def.exactLength.value : undefined) as number,
type: "array",
inclusive: true,
exact: true,
message: def.exactLength.message,
});
status.dirty();
}
}

if (def.minLength !== null) {
if (ctx.data.length < def.minLength.value) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: def.minLength.value,
type: "array",
inclusive: true,
exact: false,
message: def.minLength.message,
});
status.dirty();
Expand All @@ -1577,6 +1633,7 @@ export class ZodArray<
maximum: def.maxLength.value,
type: "array",
inclusive: true,
exact: false,
message: def.maxLength.message,
});
status.dirty();
Expand Down Expand Up @@ -1623,7 +1680,10 @@ export class ZodArray<
}

length(len: number, message?: errorUtil.ErrMessage): this {
return this.min(len, message).max(len, message) as any;
return new ZodArray({
...this._def,
exactLength: { value: len, message: errorUtil.toString(message) },
}) as any;
}

nonempty(message?: errorUtil.ErrMessage): ZodArray<T, "atleastone"> {
Expand All @@ -1638,6 +1698,7 @@ export class ZodArray<
type: schema,
minLength: null,
maxLength: null,
exactLength: null,
typeName: ZodFirstPartyTypeKind.ZodArray,
...processCreateParams(params),
});
Expand Down Expand Up @@ -2700,6 +2761,7 @@ export class ZodTuple<
code: ZodIssueCode.too_small,
minimum: this._def.items.length,
inclusive: true,
exact: false,
type: "array",
});

Expand All @@ -2713,6 +2775,7 @@ export class ZodTuple<
code: ZodIssueCode.too_big,
maximum: this._def.items.length,
inclusive: true,
exact: false,
type: "array",
});
status.dirty();
Expand Down Expand Up @@ -3011,6 +3074,7 @@ export class ZodSet<Value extends ZodTypeAny = ZodTypeAny> extends ZodType<
minimum: def.minSize.value,
type: "set",
inclusive: true,
exact: false,
message: def.minSize.message,
});
status.dirty();
Expand All @@ -3024,6 +3088,7 @@ export class ZodSet<Value extends ZodTypeAny = ZodTypeAny> extends ZodType<
maximum: def.maxSize.value,
type: "set",
inclusive: true,
exact: false,
message: def.maxSize.message,
});
status.dirty();
Expand Down
2 changes: 2 additions & 0 deletions src/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ export interface ZodTooSmallIssue extends ZodIssueBase {
code: typeof ZodIssueCode.too_small;
minimum: number;
inclusive: boolean;
exact: boolean;
type: "array" | "string" | "number" | "set" | "date";
}

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

Expand Down
36 changes: 36 additions & 0 deletions src/__tests__/validations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,42 @@ test("array max", async () => {
}
});

test("array length", async () => {
try {
await z.array(z.string()).length(2).parseAsync(["asdf", "asdf", "asdf"]);
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"Array must contain exactly 2 element(s)"
);
}

try {
await z.array(z.string()).length(2).parseAsync(["asdf"]);
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"Array must contain exactly 2 element(s)"
);
}
});

test("string length", async () => {
try {
await z.string().length(4).parseAsync("asd");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"String must contain exactly 4 character(s)"
);
}

try {
await z.string().length(4).parseAsync("asdaa");
} catch (err) {
expect((err as z.ZodError).issues[0].message).toEqual(
"String must contain exactly 4 character(s)"
);
}
});

test("string min", async () => {
try {
await z.string().min(4).parseAsync("asd");
Expand Down
Loading